From 7fc1d5783ca05f352364578ef7bf645ecc2e585f Mon Sep 17 00:00:00 2001 From: Austin Dickey Date: Wed, 17 Dec 2025 13:19:20 -0600 Subject: [PATCH 1/3] python: use 'uv add' when installing packages in certain scenarios --- .../client/common/installer/uvInstaller.ts | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/extensions/positron-python/src/client/common/installer/uvInstaller.ts b/extensions/positron-python/src/client/common/installer/uvInstaller.ts index b21aa056726b..d8f0f17f4a18 100644 --- a/extensions/positron-python/src/client/common/installer/uvInstaller.ts +++ b/extensions/positron-python/src/client/common/installer/uvInstaller.ts @@ -6,15 +6,17 @@ /* eslint-disable class-methods-use-this */ import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { ModuleInstallerType } from '../../pythonEnvironments/info'; -import { ExecutionInfo, IConfigurationService } from '../types'; -import { ModuleInstaller } from './moduleInstaller'; +import { ExecutionInfo, IConfigurationService, Product } from '../types'; +import { ModuleInstaller, translateProductToModule } from './moduleInstaller'; import { InterpreterUri, ModuleInstallFlags } from './types'; import { isUvInstalled } from '../../pythonEnvironments/common/environmentManagers/uv'; import { IServiceContainer } from '../../ioc/types'; import { isResource } from '../utils/misc'; import { IWorkspaceService } from '../application/types'; import { IInterpreterService } from '../../interpreter/contracts'; +import { IFileSystem } from '../platform/types'; @injectable() export class UVInstaller extends ModuleInstaller { @@ -54,8 +56,26 @@ export class UVInstaller extends ModuleInstaller { ): Promise { // If the resource isSupported, then the uv binary exists const execPath = 'uv'; - // TODO: should we use uv add if a pyproject.toml exists? - const args = ['pip', 'install', '--upgrade']; + + // Don't use 'uv add' for ipykernel since it's only being used to enable the Console + const isIpykernel = moduleName === translateProductToModule(Product.ipykernel); + + // ...or if we're trying to break system packages + const isBreakingSystemPackages = (flags & ModuleInstallFlags.breakSystemPackages) !== 0; + + // ...or if pyproject.toml doesn't exist at the workspace root + const workspaceService = this.serviceContainer.get(IWorkspaceService); + const fileSystem = this.serviceContainer.get(IFileSystem); + const workspaceFolder = isResource(resource) + ? workspaceService.getWorkspaceFolder(resource) + : workspaceService.workspaceFolders?.[0]; + const pyprojectPath = workspaceFolder ? path.join(workspaceFolder.uri.fsPath, 'pyproject.toml') : undefined; + const pyprojectExists = pyprojectPath ? await fileSystem.fileExists(pyprojectPath) : false; + + const usePyprojectWorkflow = + !isIpykernel && + !isBreakingSystemPackages && + pyprojectExists; // Get the path to the python interpreter (similar to a part in ModuleInstaller.installModule()) const configService = this.serviceContainer.get(IConfigurationService); @@ -64,22 +84,22 @@ export class UVInstaller extends ModuleInstaller { const interpreter = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource; const interpreterPath = interpreter?.path ?? settings.pythonPath; const pythonPath = isResource(resource) ? interpreterPath : resource.path; - args.push('--python', pythonPath); - const workspaceService = this.serviceContainer.get(IWorkspaceService); - const proxy = workspaceService.getConfiguration('http').get('proxy', ''); - if (proxy.length > 0) { - args.push('--proxy', proxy); - } + const args: string[] = []; - if (flags & ModuleInstallFlags.reInstall) { - args.push('--force-reinstall'); - } + if (usePyprojectWorkflow) { + // Use 'uv add' for project-based workflow + args.push('add'); + } else { + // Use 'uv pip install' for environment-based workflow + args.push('pip', 'install'); - // Support the --break-system-packages flag to temporarily work around PEP 668. - if (flags & ModuleInstallFlags.breakSystemPackages) { - args.push('--break-system-packages'); + // Support the --break-system-packages flag to temporarily work around PEP 668. + if (isBreakingSystemPackages) { + args.push('--break-system-packages'); + } } + args.push('--upgrade', '--python', pythonPath); return { args: [...args, moduleName], From 774528111bdca86f7e46069611a3dbcacd1794ac Mon Sep 17 00:00:00 2001 From: Austin Dickey Date: Wed, 17 Dec 2025 22:41:55 -0600 Subject: [PATCH 2/3] python: install packages using uv add --- .../src/client/common/installer/uvInstaller.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/positron-python/src/client/common/installer/uvInstaller.ts b/extensions/positron-python/src/client/common/installer/uvInstaller.ts index d8f0f17f4a18..3c480b3217ca 100644 --- a/extensions/positron-python/src/client/common/installer/uvInstaller.ts +++ b/extensions/positron-python/src/client/common/installer/uvInstaller.ts @@ -66,9 +66,10 @@ export class UVInstaller extends ModuleInstaller { // ...or if pyproject.toml doesn't exist at the workspace root const workspaceService = this.serviceContainer.get(IWorkspaceService); const fileSystem = this.serviceContainer.get(IFileSystem); - const workspaceFolder = isResource(resource) - ? workspaceService.getWorkspaceFolder(resource) - : workspaceService.workspaceFolders?.[0]; + let workspaceFolder = isResource(resource) ? workspaceService.getWorkspaceFolder(resource) : undefined; + if (!workspaceFolder && workspaceService.workspaceFolders && workspaceService.workspaceFolders.length > 0) { + workspaceFolder = workspaceService.workspaceFolders[0]; + } const pyprojectPath = workspaceFolder ? path.join(workspaceFolder.uri.fsPath, 'pyproject.toml') : undefined; const pyprojectExists = pyprojectPath ? await fileSystem.fileExists(pyprojectPath) : false; From aa4e9c62e7336cdaed2685b7c9aab444b3a54a22 Mon Sep 17 00:00:00 2001 From: Austin Dickey Date: Wed, 17 Dec 2025 22:57:17 -0600 Subject: [PATCH 3/3] lint --- .../src/client/common/installer/uvInstaller.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/extensions/positron-python/src/client/common/installer/uvInstaller.ts b/extensions/positron-python/src/client/common/installer/uvInstaller.ts index 3c480b3217ca..32c2f8155256 100644 --- a/extensions/positron-python/src/client/common/installer/uvInstaller.ts +++ b/extensions/positron-python/src/client/common/installer/uvInstaller.ts @@ -73,10 +73,7 @@ export class UVInstaller extends ModuleInstaller { const pyprojectPath = workspaceFolder ? path.join(workspaceFolder.uri.fsPath, 'pyproject.toml') : undefined; const pyprojectExists = pyprojectPath ? await fileSystem.fileExists(pyprojectPath) : false; - const usePyprojectWorkflow = - !isIpykernel && - !isBreakingSystemPackages && - pyprojectExists; + const usePyprojectWorkflow = !isIpykernel && !isBreakingSystemPackages && pyprojectExists; // Get the path to the python interpreter (similar to a part in ModuleInstaller.installModule()) const configService = this.serviceContainer.get(IConfigurationService);