Skip to content

Commit 1baccfb

Browse files
committed
feat(vscode): .env file into python env
1 parent 8018d7a commit 1baccfb

3 files changed

Lines changed: 149 additions & 3 deletions

File tree

vscode/extension/src/utilities/common/python.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { commands, Disposable, Event, EventEmitter, Uri } from 'vscode'
55
import { traceError, traceLog } from './log'
66
import { PythonExtension, ResolvedEnvironment } from '@vscode/python-extension'
77
import path from 'path'
8+
import { err, ok, Result } from '@bus/result'
9+
import * as vscode from 'vscode'
810

911
export interface IInterpreterDetails {
1012
path?: string[]
@@ -15,10 +17,12 @@ export interface IInterpreterDetails {
1517

1618
const onDidChangePythonInterpreterEvent =
1719
new EventEmitter<IInterpreterDetails>()
20+
1821
export const onDidChangePythonInterpreter: Event<IInterpreterDetails> =
1922
onDidChangePythonInterpreterEvent.event
2023

2124
let _api: PythonExtension | undefined
25+
2226
async function getPythonExtensionAPI(): Promise<PythonExtension | undefined> {
2327
if (_api) {
2428
return _api
@@ -118,3 +122,34 @@ export function checkVersion(
118122
traceError('Supported versions are 3.8 and above.')
119123
return false
120124
}
125+
126+
/**
127+
* getPythonEnvVariables returns the environment variables for the current python interpreter.
128+
*
129+
* @returns The environment variables for the current python interpreter.
130+
*/
131+
export async function getPythonEnvVariables(): Promise<
132+
Result<Record<string, string>, string>
133+
> {
134+
const api = await getPythonExtensionAPI()
135+
if (!api) {
136+
return err('Python extension API not found')
137+
}
138+
139+
const workspaces = vscode.workspace.workspaceFolders
140+
if (!workspaces) {
141+
return ok({})
142+
}
143+
const out: Record<string, string> = {}
144+
for (const workspace of workspaces) {
145+
const envVariables = api.environments.getEnvironmentVariables(workspace.uri)
146+
if (envVariables) {
147+
for (const [key, value] of Object.entries(envVariables)) {
148+
if (value) {
149+
out[key] = value
150+
}
151+
}
152+
}
153+
}
154+
return ok(out)
155+
}

vscode/extension/src/utilities/sqlmesh/sqlmesh.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'path'
22
import { traceInfo, traceLog, traceVerbose } from '../common/log'
3-
import { getInterpreterDetails } from '../common/python'
3+
import { getInterpreterDetails, getPythonEnvVariables } from '../common/python'
44
import { Result, err, isErr, ok } from '@bus/result'
55
import { getProjectRoot } from '../common/utilities'
66
import { isPythonModuleInstalled } from '../python'
@@ -230,6 +230,13 @@ export const sqlmeshExec = async (): Promise<
230230
message: resolvedPath.error,
231231
})
232232
}
233+
const envVariables = await getPythonEnvVariables()
234+
if (isErr(envVariables)) {
235+
return err({
236+
type: 'generic',
237+
message: envVariables.error,
238+
})
239+
}
233240
const workspacePath = resolvedPath.value
234241
const interpreterDetails = await getInterpreterDetails()
235242
traceLog(`Interpreter details: ${JSON.stringify(interpreterDetails)}`)
@@ -271,6 +278,8 @@ export const sqlmeshExec = async (): Promise<
271278
PYTHONPATH: interpreterDetails.path?.[0],
272279
VIRTUAL_ENV: path.dirname(interpreterDetails.binPath!),
273280
PATH: interpreterDetails.binPath!,
281+
...process.env,
282+
...envVariables.value,
274283
},
275284
args: [],
276285
})
@@ -284,6 +293,8 @@ export const sqlmeshExec = async (): Promise<
284293
PYTHONPATH: interpreterDetails.path?.[0],
285294
VIRTUAL_ENV: path.dirname(path.dirname(interpreterDetails.binPath!)), // binPath now points to bin dir
286295
PATH: interpreterDetails.binPath!,
296+
...process.env,
297+
...envVariables.value,
287298
},
288299
args: [],
289300
})
@@ -297,7 +308,10 @@ export const sqlmeshExec = async (): Promise<
297308
return ok({
298309
bin: sqlmesh,
299310
workspacePath,
300-
env: {},
311+
env: {
312+
...process.env,
313+
...envVariables.value,
314+
},
301315
args: [],
302316
})
303317
}
@@ -353,6 +367,13 @@ export const sqlmeshLspExec = async (): Promise<
353367
> => {
354368
const sqlmeshLSP = IS_WINDOWS ? 'sqlmesh_lsp.exe' : 'sqlmesh_lsp'
355369
const projectRoot = await getProjectRoot()
370+
const envVariables = await getPythonEnvVariables()
371+
if (isErr(envVariables)) {
372+
return err({
373+
type: 'generic',
374+
message: envVariables.error,
375+
})
376+
}
356377
const resolvedPath = resolveProjectPath(projectRoot)
357378
if (isErr(resolvedPath)) {
358379
return err({
@@ -408,6 +429,8 @@ export const sqlmeshLspExec = async (): Promise<
408429
PYTHONPATH: interpreterDetails.path?.[0],
409430
VIRTUAL_ENV: path.dirname(interpreterDetails.binPath!),
410431
PATH: interpreterDetails.binPath!,
432+
...process.env,
433+
...envVariables.value,
411434
},
412435
args: ['sqlmesh_lsp'],
413436
})
@@ -431,6 +454,8 @@ export const sqlmeshLspExec = async (): Promise<
431454
PYTHONPATH: interpreterDetails.path?.[0],
432455
VIRTUAL_ENV: path.dirname(path.dirname(interpreterDetails.binPath!)), // binPath now points to bin dir
433456
PATH: interpreterDetails.binPath!, // binPath already points to the bin directory
457+
...process.env,
458+
...envVariables.value,
434459
},
435460
args: [],
436461
})
@@ -444,7 +469,10 @@ export const sqlmeshLspExec = async (): Promise<
444469
return ok({
445470
bin: sqlmeshLSP,
446471
workspacePath,
447-
env: {},
472+
env: {
473+
...process.env,
474+
...envVariables.value,
475+
},
448476
args: [],
449477
})
450478
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { test } from '@playwright/test'
2+
import fs from 'fs-extra'
3+
import {
4+
createVirtualEnvironment,
5+
openLineageView,
6+
pipInstall,
7+
REPO_ROOT,
8+
startVSCode,
9+
SUSHI_SOURCE_PATH,
10+
} from './utils'
11+
import os from 'os'
12+
import path from 'path'
13+
14+
function writeEnvironmentConfig(sushi_path: string) {
15+
const config_path = path.join(sushi_path, 'config.py')
16+
const original_config = fs.readFileSync(config_path, 'utf8')
17+
18+
const new_config =
19+
`
20+
import os
21+
22+
test_var = os.getenv("TEST_VAR")
23+
if test_var is None or test_var == "":
24+
raise Exception("TEST_VAR is not set")
25+
` + original_config
26+
27+
fs.writeFileSync(config_path, new_config)
28+
}
29+
30+
async function setupEnvironment(): Promise<string> {
31+
const tempDir = await fs.mkdtemp(
32+
path.join(os.tmpdir(), 'vscode-test-tcloud-'),
33+
)
34+
await fs.copy(SUSHI_SOURCE_PATH, tempDir)
35+
const pythonEnvDir = path.join(tempDir, '.venv')
36+
const pythonDetails = await createVirtualEnvironment(pythonEnvDir)
37+
const custom_materializations = path.join(
38+
REPO_ROOT,
39+
'examples',
40+
'custom_materializations',
41+
)
42+
const sqlmeshWithExtras = `${REPO_ROOT}[bigquery,lsp]`
43+
await pipInstall(pythonDetails, [sqlmeshWithExtras, custom_materializations])
44+
45+
const settings = {
46+
'python.defaultInterpreterPath': pythonDetails.pythonPath,
47+
'sqlmesh.environmentPath': pythonEnvDir,
48+
}
49+
await fs.ensureDir(path.join(tempDir, '.vscode'))
50+
await fs.writeJson(path.join(tempDir, '.vscode', 'settings.json'), settings, {
51+
spaces: 2,
52+
})
53+
54+
return tempDir
55+
}
56+
57+
test.describe('python environment variable injection on sqlmesh_lsp', () => {
58+
test('normal setup - error ', async () => {
59+
const tempDir = await setupEnvironment()
60+
writeEnvironmentConfig(tempDir)
61+
const { window, close } = await startVSCode(tempDir)
62+
try {
63+
await openLineageView(window)
64+
await window.waitForSelector('text=Error creating context')
65+
} finally {
66+
await close()
67+
}
68+
})
69+
70+
test('normal setup - set', async () => {
71+
const tempDir = await setupEnvironment()
72+
writeEnvironmentConfig(tempDir)
73+
const env_file = path.join(tempDir, '.env')
74+
fs.writeFileSync(env_file, 'TEST_VAR=test_value')
75+
const { window, close } = await startVSCode(tempDir)
76+
try {
77+
await openLineageView(window)
78+
await window.waitForSelector('text=Loaded SQLMesh context')
79+
} finally {
80+
await close()
81+
}
82+
})
83+
})

0 commit comments

Comments
 (0)