Skip to content

Commit 31a42e4

Browse files
authored
Use UV to manage venv dependencies when it's available (#1673)
## Changes If `uv` is accessible and if the project root has `uv.lock` file, we use `uv` command to manage dependencies in the venv. Should fix #1647 ## Tests Existing e2e tests for native pip flow, manual tests for new uv flow
1 parent 55dd5f7 commit 31a42e4

File tree

1 file changed

+58
-67
lines changed

1 file changed

+58
-67
lines changed

packages/databricks-vscode/src/language/MsPythonExtensionWrapper.ts

Lines changed: 58 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as childProcess from "node:child_process";
1515
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
1616
import {execFile} from "../cli/CliWrapper";
1717
import fs from "node:fs";
18+
import path from "node:path";
1819

1920
export class MsPythonExtensionWrapper implements Disposable {
2021
public readonly api: MsPythonExtensionApi;
@@ -95,6 +96,10 @@ export class MsPythonExtensionWrapper implements Disposable {
9596
return this.api.environments?.resolveEnvironment(env);
9697
}
9798

99+
get projectRoot() {
100+
return this.workspaceFolderManager.activeProjectUri.fsPath;
101+
}
102+
98103
async runWithOutput(
99104
command: string,
100105
args: string[],
@@ -115,69 +120,62 @@ export class MsPythonExtensionWrapper implements Disposable {
115120
});
116121
}
117122

118-
async getLatestPackageVersion(name: string) {
119-
const executable = await this.getPythonExecutable();
120-
if (!executable) {
121-
return;
123+
async isUsingUv() {
124+
try {
125+
await execFile("uv", ["--version"]);
126+
return fs.existsSync(path.join(this.projectRoot, "uv.lock"));
127+
} catch (error) {
128+
return false;
122129
}
123-
const {stdout} = await execFile(
124-
executable,
125-
[
130+
}
131+
132+
private async getPipCommandAndArgs(
133+
executable: string,
134+
baseArgs: string[],
135+
nativePipArgs: string[] = []
136+
): Promise<{command: string; args: string[]}> {
137+
const isUv = await this.isUsingUv();
138+
if (isUv) {
139+
return {
140+
command: "uv",
141+
args: ["pip", ...baseArgs, "--python", executable],
142+
};
143+
}
144+
return {
145+
command: executable,
146+
args: [
126147
"-m",
127148
"pip",
128-
"index",
129-
"versions",
130-
name,
149+
...baseArgs,
150+
...nativePipArgs,
131151
"--disable-pip-version-check",
132152
"--no-python-version-warning",
133153
],
134-
{shell: false}
135-
);
136-
const match = stdout.match(/.+\((.+)\)/);
137-
if (match) {
138-
return match[1];
139-
}
154+
};
140155
}
141156

142-
async getPackageDetailsFromEnvironment(
143-
name: string,
144-
version?: string | RegExp
145-
) {
157+
async getPackageDetailsFromEnvironment(name: string) {
146158
const executable = await this.getPythonExecutable();
147159
if (!executable) {
148160
return undefined;
149161
}
150-
if (version === "latest") {
151-
version = await this.getLatestPackageVersion(name);
152-
}
153162

154-
const {stdout} = await execFile(
155-
executable,
156-
[
157-
"-m",
158-
"pip",
159-
"list",
160-
"--format",
161-
"json",
162-
"--disable-pip-version-check",
163-
"--no-python-version-warning",
164-
],
165-
{shell: false}
166-
);
163+
const {command, args} = await this.getPipCommandAndArgs(executable, [
164+
"list",
165+
"--format",
166+
"json",
167+
]);
167168

169+
const {stdout} = await execFile(command, args, {
170+
shell: false,
171+
});
168172
const data: Array<{name: string; version: string}> = JSON.parse(stdout);
169-
return data.find(
170-
(item) =>
171-
item.name === name &&
172-
(version === undefined ||
173-
item.version.match(version) !== undefined)
174-
);
173+
return data.find((item) => item.name === name);
175174
}
176175

177-
async findPackageInEnvironment(name: string, version?: string | RegExp) {
176+
async findPackageInEnvironment(name: string) {
178177
return (
179-
(await this.getPackageDetailsFromEnvironment(name, version)) !==
180-
undefined
178+
(await this.getPackageDetailsFromEnvironment(name)) !== undefined
181179
);
182180
}
183181

@@ -190,19 +188,14 @@ export class MsPythonExtensionWrapper implements Disposable {
190188
if (!executable) {
191189
throw Error("No python executable found");
192190
}
193-
if (version === "latest") {
194-
version = await this.getLatestPackageVersion(name);
195-
}
196-
const args = [
197-
"-m",
198-
"pip",
191+
192+
const {command, args} = await this.getPipCommandAndArgs(executable, [
199193
"install",
200194
`${name}${version ? `==${version}` : ""}`,
201-
"--disable-pip-version-check",
202-
"--no-python-version-warning",
203-
];
204-
outputChannel?.appendLine(`Running: ${executable} ${args.join(" ")}`);
205-
await this.runWithOutput(executable, args, outputChannel);
195+
]);
196+
197+
outputChannel?.appendLine(`Running: ${command} ${args.join(" ")}`);
198+
await this.runWithOutput(command, args, outputChannel);
206199
}
207200

208201
async uninstallPackageFromEnvironment(
@@ -214,17 +207,15 @@ export class MsPythonExtensionWrapper implements Disposable {
214207
if (!exists || !executable) {
215208
return;
216209
}
217-
const args = [
218-
"-m",
219-
"pip",
220-
"uninstall",
221-
name,
222-
"--disable-pip-version-check",
223-
"--no-python-version-warning",
224-
"-y",
225-
];
226-
outputChannel?.appendLine(`Running: ${executable} ${args.join(" ")}`);
227-
await this.runWithOutput(executable, args, outputChannel);
210+
211+
const {command, args} = await this.getPipCommandAndArgs(
212+
executable,
213+
["uninstall", name],
214+
["-y"]
215+
);
216+
217+
outputChannel?.appendLine(`Running: ${command} ${args.join(" ")}`);
218+
await this.runWithOutput(command, args, outputChannel);
228219
}
229220

230221
async selectPythonInterpreter() {

0 commit comments

Comments
 (0)