Skip to content

Commit a720b6e

Browse files
authored
Add dedicated loader for command execution API (#257)
This fixes the wrong line numbers in python stack traces
1 parent ec9884a commit a720b6e

File tree

7 files changed

+124
-115
lines changed

7 files changed

+124
-115
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import runpy
2+
import sys
3+
import os
4+
5+
# values will be injected by the runner
6+
python_file = "PYTHON_FILE"
7+
repo_path = "REPO_PATH"
8+
args = []
9+
env = {}
10+
11+
# change working directory
12+
os.chdir(os.path.dirname(python_file))
13+
14+
# update python path
15+
sys.path.append(repo_path)
16+
sys.path.append(os.path.dirname(python_file))
17+
18+
# inject command line arguments
19+
sys.argv = args
20+
21+
# inject environment variables
22+
for key in env:
23+
os.environ[key] = env[key]
24+
25+
# provide spark globals
26+
user_ns = {
27+
"display": display,
28+
"displayHTML": displayHTML,
29+
"dbutils": dbutils,
30+
"table": table,
31+
"sql": sql,
32+
"udf": udf,
33+
"getArgument": getArgument,
34+
"sc": sc,
35+
"spark": spark,
36+
"sqlContext": sqlContext,
37+
}
38+
39+
# Set log level to "ERROR". See https://kb.databricks.com/notebooks/cmd-c-on-object-id-p0.html
40+
import logging; logger = spark._jvm.org.apache.log4j;
41+
logging.getLogger("py4j.java_gateway").setLevel(logging.ERROR)
42+
43+
runpy.run_path(python_file, run_name="__main__", init_globals=user_ns)
44+
None

packages/databricks-vscode/src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ export async function activate(
129129
const runCommands = new RunCommands(connectionManager, synchronizer);
130130
const debugFactory = new DatabricksDebugAdapterFactory(
131131
connectionManager,
132-
synchronizer
132+
synchronizer,
133+
context
133134
);
134135
const debugWorkflowFactory = new DatabricksWorkflowDebugAdapterFactory(
135136
connectionManager,

packages/databricks-vscode/src/run/DatabricksDebugAdapter.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,20 @@ import {
88
} from "@vscode/debugadapter";
99
import {DebugProtocol} from "@vscode/debugprotocol";
1010
import {basename} from "node:path";
11-
import {TextDecoder} from "node:util";
1211

1312
import {
1413
DebugAdapterDescriptor,
1514
DebugAdapterDescriptorFactory,
1615
DebugAdapterInlineImplementation,
1716
Disposable,
1817
ProviderResult,
19-
Uri,
2018
commands,
2119
window,
22-
workspace,
20+
ExtensionContext,
2321
} from "vscode";
2422
import {ConnectionManager} from "../configuration/ConnectionManager";
2523
import {CodeSynchronizer} from "../sync/CodeSynchronizer";
26-
import {DatabricksRuntime, FileAccessor} from "./DatabricksRuntime";
24+
import {DatabricksRuntime} from "./DatabricksRuntime";
2725
import {Subject} from "./Subject";
2826

2927
/**
@@ -48,42 +46,39 @@ export class DatabricksDebugAdapterFactory
4846
{
4947
constructor(
5048
private connection: ConnectionManager,
51-
private codeSynchroniser: CodeSynchronizer
49+
private codeSynchroniser: CodeSynchronizer,
50+
private context: ExtensionContext
5251
) {}
5352

5453
dispose() {}
5554

5655
createDebugAdapterDescriptor(): ProviderResult<DebugAdapterDescriptor> {
5756
return new DebugAdapterInlineImplementation(
58-
new DatabricksDebugSession(this.connection, this.codeSynchroniser)
57+
new DatabricksDebugSession(
58+
this.connection,
59+
this.codeSynchroniser,
60+
this.context
61+
)
5962
);
6063
}
6164
}
6265

63-
export const workspaceFileAccessor: FileAccessor = {
64-
async readFile(path: string): Promise<string> {
65-
const uri = Uri.file(path);
66-
67-
const bytes = await workspace.fs.readFile(uri);
68-
return new TextDecoder().decode(bytes);
69-
},
70-
};
71-
7266
export class DatabricksDebugSession extends LoggingDebugSession {
7367
private runtime: DatabricksRuntime;
7468
private _configurationDone = new Subject();
7569
private disposables: Disposable[] = [];
7670

7771
constructor(
7872
connection: ConnectionManager,
79-
codeSynchronizer: CodeSynchronizer
73+
codeSynchronizer: CodeSynchronizer,
74+
context: ExtensionContext
8075
) {
8176
super();
8277

8378
this.runtime = new DatabricksRuntime(
8479
connection,
85-
workspaceFileAccessor,
86-
codeSynchronizer
80+
codeSynchronizer,
81+
context
8782
);
8883

8984
this.disposables.push(

packages/databricks-vscode/src/run/DatabricksRuntime.test.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,18 @@
22

33
import assert from "assert";
44
import {mock, when, instance, anything, verify, capture} from "ts-mockito";
5-
import {Disposable, Uri} from "vscode";
5+
import {Disposable, ExtensionContext, Uri} from "vscode";
66
import {
77
Cluster,
88
Command,
99
ExecutionContext,
1010
Repo,
1111
} from "@databricks/databricks-sdk";
12-
import {
13-
DatabricksRuntime,
14-
FileAccessor,
15-
OutputEvent,
16-
} from "./DatabricksRuntime";
12+
import {DatabricksRuntime, OutputEvent} from "./DatabricksRuntime";
1713
import {ConnectionManager} from "../configuration/ConnectionManager";
1814
import {SyncDestination} from "../configuration/SyncDestination";
1915
import {CodeSynchronizer} from "../sync/CodeSynchronizer";
16+
import path from "node:path";
2017

2118
describe(__filename, () => {
2219
let disposables: Array<Disposable>;
@@ -66,20 +63,19 @@ describe(__filename, () => {
6663
);
6764
when(connectionManagerMock.syncDestination).thenReturn(syncDestination);
6865

69-
const fileAccessor: FileAccessor = {
70-
async readFile(): Promise<string> {
71-
return "print('43')";
72-
},
73-
};
74-
7566
const connectionManager = instance<ConnectionManager>(
7667
connectionManagerMock
7768
);
7869

70+
const extensionContextMock = mock<ExtensionContext>();
71+
when(extensionContextMock.extensionUri).thenReturn(
72+
Uri.file(path.join(__dirname, "..", ".."))
73+
);
74+
7975
runtime = new DatabricksRuntime(
8076
connectionManager,
81-
fileAccessor,
82-
mock(CodeSynchronizer)
77+
mock(CodeSynchronizer),
78+
instance(extensionContextMock)
8379
);
8480
});
8581

@@ -102,7 +98,6 @@ describe(__filename, () => {
10298
await runtime.start("/Desktop/workspaces/hello.py", [], {});
10399

104100
verify(connectionManagerMock.waitForConnect()).called();
105-
106101
assert.equal(outputs.length, 6);
107102
assert(outputs[0].text.includes("Connecting to cluster"));
108103
assert(
@@ -114,34 +109,18 @@ describe(__filename, () => {
114109
assert(outputs[5].text.includes("Done"));
115110
});
116111

117-
it("should have the right code with env vars", async () => {
112+
it("should inject environment variables", async () => {
118113
await runtime.start("/Desktop/workspaces/hello.py", [], {TEST: "TEST"});
119114

120115
const code = capture(executionContextMock.execute).first()[0];
121-
assert.equal(
122-
code,
123-
`import os; os.chdir("/Workspace/Repos/[email protected]/test");
124-
import sys; sys.path.append("/Workspace/Repos/[email protected]/test")
125-
import sys; sys.argv = ['/Workspace/Repos/[email protected]/test/hello.py'];
126-
import os; os.environ["TEST"]='TEST';
127-
import logging; logger = spark._jvm.org.apache.log4j; logging.getLogger("py4j.java_gateway").setLevel(logging.ERROR)
128-
print('43')`
129-
);
116+
assert(code.includes(`env = {"TEST":"TEST"}`));
130117
});
131118

132119
it("should have the right code without env vars", async () => {
133120
await runtime.start("/Desktop/workspaces/hello.py", [], {});
134121

135122
const code = capture(executionContextMock.execute).first()[0];
136-
assert.equal(
137-
code,
138-
`import os; os.chdir("/Workspace/Repos/[email protected]/test");
139-
import sys; sys.path.append("/Workspace/Repos/[email protected]/test")
140-
import sys; sys.argv = ['/Workspace/Repos/[email protected]/test/hello.py'];
141-
142-
import logging; logger = spark._jvm.org.apache.log4j; logging.getLogger("py4j.java_gateway").setLevel(logging.ERROR)
143-
print('43')`
144-
);
123+
assert(code.includes(`env = {}`));
145124
});
146125

147126
it("should handle failed executions", async () => {

0 commit comments

Comments
 (0)