From 86de652918349127a4b709ccf60c2178db3d2107 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Thu, 17 Sep 2026 11:59:50 -0600 Subject: [PATCH 1/6] Add API Docs --- docs/README.md | 1126 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1126 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..72fe45e2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,1126 @@ +# Python Environments API + +The `@vscode/python-environments` package lets VS Code extensions consume and +extend the API exposed by the +[Python Environments extension](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-python-envs). + +Use this manual to: + +- discover, select, create, and remove Python environments; +- inspect and manage installed packages; +- run Python in terminals, tasks, or background processes; +- work with Python projects and environment variables; or +- contribute an environment manager, package manager, or project creator. + +The authoritative API declarations are in [`src/api.ts`](../src/api.ts). + +> [!IMPORTANT] +> The API is flat. Call `api.getEnvironments()`, not +> `api.environments.getEnvironments()`. The smaller API interfaces organize the +> TypeScript declarations; they are not nested runtime objects. + +## Contents + +- [Get started](#get-started) +- [Environment methods](#environment-methods) +- [Package methods](#package-methods) +- [Project methods](#project-methods) +- [Execution methods](#execution-methods) +- [Environment variable methods](#environment-variable-methods) +- [Provider methods](#provider-methods) +- [Errors and lifecycle](#errors-and-lifecycle) +- [Shared types](#shared-types) +- [Compatibility guidance](#compatibility-guidance) + +## Get started + +### Install the package + +Declare the Python Environments extension as a dependency of your extension: + +```jsonc +{ + "extensionDependencies": ["ms-python.vscode-python-envs"] +} +``` + +Then install the API package: + +```console +npm install @vscode/python-environments +``` + +The npm package provides the public types and the helper used to acquire the +API. The Python Environments VS Code extension provides the implementation at +runtime. + +### `PythonEnvironments.api` + +Call `PythonEnvironments.api()` during activation: + +```typescript +import * as vscode from 'vscode'; +import { + PythonEnvironmentApi, + PythonEnvironments, +} from '@vscode/python-environments'; + +export async function activate( + context: vscode.ExtensionContext, +): Promise { + const api: PythonEnvironmentApi = await PythonEnvironments.api(); + + const environments = await api.getEnvironments('all'); + // Use the API or pass it to services owned by the extension. +} +``` + +`PythonEnvironments.api()`: + +1. Finds the extension with ID `ms-python.vscode-python-envs`. +2. Activates it when necessary. +3. Returns its `PythonEnvironmentApi`. + +It rejects when the extension is missing or disabled, activation fails, or the +extension does not expose its API. Acquire the object once and reuse it. + +### API areas + +`PythonEnvironmentApi` combines the following interfaces into one object: + +| Area | Representative members | +| --- | --- | +| Environments | `getEnvironments`, `resolveEnvironment`, `setEnvironment`, `createEnvironment` | +| Packages | `getPackages`, `managePackages`, `getPackageAvailableVersions` | +| Projects | `getPythonProjects`, `getPythonProject`, `addPythonProject` | +| Execution | `createTerminal`, `runInTerminal`, `runAsTask`, `runInBackground` | +| Environment variables | `getEnvironmentVariables`, `onDidChangeEnvironmentVariables` | +| Provider registration | `registerEnvironmentManager`, `registerPackageManager`, `registerPythonProjectCreator` | + +## Environment methods + +Environment methods discover, resolve, create, remove, and select Python +environments. + +### Scopes + +Many API calls use a scope to identify a project, workspace, manager, or global +state. Pass a `vscode.Uri` rather than a filesystem path whenever a method +accepts a URI. + +| Type | Values | Used by | +| --- | --- | --- | +| `GetEnvironmentsScope` | `Uri`, `'all'`, or `'global'` | `getEnvironments` | +| `RefreshEnvironmentsScope` | `Uri` or `undefined` | `refreshEnvironments` | +| `GetEnvironmentScope` | `Uri` or `undefined` | `getEnvironment` | +| `SetEnvironmentScope` | `Uri`, `Uri[]`, or `undefined` | `setEnvironment` | +| `CreateEnvironmentScope` | `Uri`, `Uri[]`, or `'global'` | `createEnvironment` | +| `ResolveEnvironmentContext` | `Uri` | `resolveEnvironment` | + +The scope values mean: + +- **`Uri`**: the workspace, folder, file, environment directory, or Python + executable relevant to that method. +- **`Uri[]`**: apply an environment operation to multiple URI scopes. +- **`'all'`**: return all discovered environments. +- **`'global'`**: address global Python installations or global environment + creation. +- **`undefined` for selection**: get or set the global environment selection. +- **`undefined` for refresh**: refresh global and workspace discovery. +- **`undefined` for environment variables**: resolve variables for global + scope. + +`getEnvironments()` does not accept `undefined`. Use a URI, `'all'`, or +`'global'`. + +`resolveEnvironment()` currently accepts only a `Uri`. Use the exported +signature as the contract even though older documentation may describe other +inputs. + +### `PythonEnvironment` and identity + +A `PythonEnvironment` contains an `envId`: + +```typescript +interface PythonEnvironmentId { + id: string; + managerId: string; +} +``` + +- `envId.id` identifies the environment within its manager. +- `envId.managerId` identifies the manager that owns the environment. + +Use both values when storing or comparing identities: + +```typescript +import { PythonEnvironment } from '@vscode/python-environments'; + +function environmentKey(environment: PythonEnvironment): string { + return `${environment.envId.managerId}:${environment.envId.id}`; +} +``` + +Do not read `environment.id`; the identifier is `environment.envId`. + +### `getEnvironments` + +```typescript +getEnvironments( + scope: Uri | 'all' | 'global', +): Promise +``` + +Returns environments associated with a URI, global Python installations, or +all discovered environments. The result is always an array. + +```typescript +const all = await api.getEnvironments('all'); +const globalInstallations = await api.getEnvironments('global'); +const projectEnvironments = await api.getEnvironments(projectUri); +``` + +Each result contains the following `PythonEnvironmentInfo` plus `envId`: + +| Property | Type | Required | +| --- | --- | --- | +| `name` | `string` | Yes | +| `displayName` | `string` | Yes | +| `displayPath` | `string` | Yes | +| `version` | `string` | Yes | +| `environmentPath` | `Uri` | Yes | +| `execInfo` | `PythonEnvironmentExecutionInfo` | Yes | +| `sysPrefix` | `string` | Yes | +| `shortDisplayName` | `string` | No | +| `description` | `string` | No | +| `tooltip` | `string \| MarkdownString` | No | +| `iconPath` | `IconPath` | No | +| `group` | `string \| EnvironmentGroupInfo` | No | +| `error` | `string` | No | + +### `refreshEnvironments` + +```typescript +refreshEnvironments(scope: Uri | undefined): Promise +``` + +Refreshes discovery for a URI. Pass `undefined` to refresh global and workspace +discovery. + +```typescript +await api.refreshEnvironments(projectUri); +await api.refreshEnvironments(undefined); +``` + +### `resolveEnvironment` + +```typescript +resolveEnvironment( + context: Uri, +): Promise +``` + +Resolves an environment directory or Python executable URI. It returns +`undefined` when no manager can resolve the URI. The current exported signature +accepts only `Uri`. + +```typescript +const resolved = await api.resolveEnvironment(pythonExecutableUri); +if (resolved === undefined) { + // No registered manager resolved the URI. +} +``` + +### `getEnvironment` + +```typescript +getEnvironment( + scope: Uri | undefined, +): Promise +``` + +Gets the selected environment for a URI. Pass `undefined` for the global +selection. It returns `undefined` when no environment is selected. + +```typescript +const selected = await api.getEnvironment(projectUri); +const globalSelection = await api.getEnvironment(undefined); +``` + +### `setEnvironment` + +```typescript +setEnvironment( + scope: Uri | Uri[] | undefined, + environment?: PythonEnvironment, +): Promise +``` + +Sets the selected environment for one or more URI scopes, or for global scope +when `scope` is `undefined`. Omit `environment` to clear the selection. + +```typescript +await api.setEnvironment(projectUri, environment); +await api.setEnvironment([applicationUri, testsUri], environment); +await api.setEnvironment(projectUri, undefined); +``` + +### `createEnvironment` + +```typescript +createEnvironment( + scope: Uri | Uri[] | 'global', + options?: CreateEnvironmentOptions, +): Promise +``` + +Creates an environment through the manager associated with the scope. It +returns `undefined` when no environment is created. + +```typescript +const environment = await api.createEnvironment(projectUri, { + quickCreate: true, + additionalPackages: ['pytest'], +}); + +if (environment !== undefined) { + await api.setEnvironment(projectUri, environment); +} +``` + +`CreateEnvironmentOptions` supports: + +| Property | Meaning | +| --- | --- | +| `quickCreate: true` | Request creation without user input or prompts. | +| `quickCreate: false` | Permit prompts and indicate that quick create was explicitly skipped. | +| `quickCreate: undefined` | Permit prompts and allow the manager to offer quick create. | +| `additionalPackages` | Install these packages in addition to packages chosen during creation. | + +### `removeEnvironment` + +```typescript +removeEnvironment( + environment: PythonEnvironment, + options?: RemoveEnvironmentOptions, +): Promise +``` + +Removes an environment through its owning manager. + +```typescript +await api.removeEnvironment(environment, { + runHeadless: true, +}); +``` + +`RemoveEnvironmentOptions.runHeadless` requests removal without a confirmation +prompt. + +### `onDidChangeEnvironments` + +```typescript +onDidChangeEnvironments: Event<{ + kind: EnvironmentChangeKind; + environment: PythonEnvironment; +}[]> +``` + +Fires with one or more `add` or `remove` changes to discovered environments. + +### `onDidChangeEnvironment` + +```typescript +onDidChangeEnvironment: Event<{ + readonly uri: Uri | undefined; + readonly old: PythonEnvironment | undefined; + readonly new: PythonEnvironment | undefined; +}> +``` + +Fires when the selected environment changes. `uri` is `undefined` for a global +selection change. + +## Package methods + +Package operations use the package manager associated with a +`PythonEnvironment`. + +### `getPackages` + +```typescript +getPackages( + environment: PythonEnvironment, + options?: GetPackagesOptions, +): Promise +``` + +Gets installed packages. It returns `undefined` when package information is +unavailable. Set `skipCache: true` to query the underlying package tool. + +```typescript +const cachedPackages = await api.getPackages(environment); +const currentPackages = await api.getPackages(environment, { + skipCache: true, +}); +``` + +`Package` extends `PackageInfo` with a `pkgId` containing `id`, `managerId`, and +`environmentId`. `PackageInfo` contains: + +| Property | Type | Required | +| --- | --- | --- | +| `name` | `string` | Yes | +| `displayName` | `string` | Yes | +| `version` | `string` | No | +| `description` | `string` | No | +| `tooltip` | `string \| MarkdownString` | No | +| `iconPath` | `IconPath` | No | +| `uris` | `readonly Uri[]` | No | +| `isTransitive` | `boolean` | No | + +### `refreshPackages` + +```typescript +refreshPackages(environment: PythonEnvironment): Promise +``` + +Refreshes package information for an environment. + +```typescript +await api.refreshPackages(environment); +``` + +### `managePackages` + +```typescript +managePackages( + environment: PythonEnvironment, + options: PackageManagementOptions, +): Promise +``` + +`PackageManagementOptions` requires `install`, `uninstall`, or both: + +```typescript +await api.managePackages(environment, { + install: ['requests', 'pytest'], + upgrade: true, +}); + +await api.managePackages(environment, { + uninstall: ['requests'], +}); + +await api.managePackages(environment, { + install: ['requests'], + uninstall: ['urllib3'], + runHeadless: true, +}); +``` + +| Option | Meaning | +| --- | --- | +| `install` | Package names or install arguments. | +| `uninstall` | Package names to uninstall. | +| `upgrade` | Upgrade packages that are already installed. | +| `showSkipOption` | Let an interactive flow offer to skip the operation. | +| `runHeadless` | Run without prompts and rely on the supplied package lists. | + +### `getPackageAvailableVersions` + +```typescript +getPackageAvailableVersions( + environment: PythonEnvironment, + packageName: string, + options: { errorMode: 'throw' }, +): Promise + +getPackageAvailableVersions( + environment: PythonEnvironment, + packageName: string, + options?: { errorMode?: 'legacy' | 'throw' }, +): Promise +``` + +The default, legacy mode returns `undefined` when lookup is unsupported or +fails: + +```typescript +const versions = await api.getPackageAvailableVersions( + environment, + 'requests', +); +``` + +New integrations should use throw mode when they need to distinguish an +unsupported capability from an operational failure: + +```typescript +import { + isPackageVersionLookupNotSupportedError, +} from '@vscode/python-environments'; + +try { + const versions = await api.getPackageAvailableVersions( + environment, + 'requests', + { errorMode: 'throw' }, + ); +} catch (error) { + if (isPackageVersionLookupNotSupportedError(error)) { + // Offer manual version entry or hide version suggestions. + } else { + throw error; + } +} +``` + +With `{ errorMode: 'throw' }`, unsupported lookup rejects with +`PackageVersionLookupNotSupportedError`; operational failures propagate +unchanged. Use the exported type guard instead of relying only on `instanceof`, +because extensions may bundle separate copies of the API package. +The error exposes the stable code `PackageVersionLookupNotSupported`. + +### `onDidChangePackages` + +```typescript +onDidChangePackages: Event<{ + environment: PythonEnvironment; + manager: PackageManager; + changes: { kind: PackageChangeKind; pkg: Package }[]; +}> +``` + +Fires when packages are added or removed. `PackageChangeKind` contains `add` +and `remove`. + +## Project methods + +A `PythonProject` represents a folder or file that can have its own Python +environment. Workspace folders are projects by default. + +```typescript +interface PythonProject { + readonly name: string; + readonly uri: Uri; + readonly description?: string; + readonly tooltip?: string | MarkdownString; +} +``` + +### `getPythonProjects` + +```typescript +getPythonProjects(): readonly PythonProject[] +``` + +Synchronously returns all known projects. + +### `getPythonProject` + +```typescript +getPythonProject(uri: Uri): PythonProject | undefined +``` + +Synchronously returns the project associated with a URI. + +```typescript +const projects = api.getPythonProjects(); +const project = api.getPythonProject(document.uri); + +if (project !== undefined) { + const environment = await api.getEnvironment(project.uri); +} +``` + +### `addPythonProject` + +```typescript +addPythonProject( + projects: PythonProject | PythonProject[], +): void +``` + +```typescript +import { PythonProject } from '@vscode/python-environments'; + +const project: PythonProject = { + name: 'Backend', + uri: backendUri, + description: 'Backend service', +}; + +api.addPythonProject(project); +``` + +### `removePythonProject` + +```typescript +removePythonProject(project: PythonProject): void +``` + +`removePythonProject()` removes the project from tracking; it does not describe +a filesystem deletion operation. + +### `onDidChangePythonProjects` + +```typescript +onDidChangePythonProjects: Event<{ + added: PythonProject[]; + removed: PythonProject[]; +}> +``` + +Fires after projects are added to or removed from the tracked collection. + +See [Making and Managing Python Projects](managing-python-projects.md) for +project-oriented user workflows. + +## Execution methods + +All execution methods require a `PythonEnvironment`. + +### `PythonEnvironmentExecutionInfo` + +The environment describes execution with a required `run` command and optional +`activatedRun`, `activation`, `shellActivation`, `deactivation`, and +`shellDeactivation` commands. Each `PythonCommandRunConfiguration` contains an +absolute, spawnable `executable` and optional `args`. + +### `createTerminal` + +```typescript +createTerminal( + environment: PythonEnvironment, + options: PythonTerminalCreateOptions, +): Promise +``` + +`PythonTerminalCreateOptions` extends VS Code's `TerminalOptions` and adds +`disableActivation?: boolean`. + +```typescript +const terminal = await api.createTerminal(environment, { + name: 'Python tools', + cwd: project.uri, + disableActivation: false, +}); + +terminal.show(); +``` + +### `runInTerminal` + +```typescript +runInTerminal( + environment: PythonEnvironment, + options: PythonTerminalExecutionOptions, +): Promise +``` + +Runs Python in an available project terminal, creating one when necessary. +`PythonTerminalExecutionOptions` requires `cwd: string | Uri` and optionally +accepts `args: string[]` and `show: boolean`. + +```typescript +await api.runInTerminal(environment, { + cwd: project.uri, + args: ['script.py', '--verbose'], + show: true, +}); +``` + +### `runInDedicatedTerminal` + +```typescript +runInDedicatedTerminal( + terminalKey: Uri | string, + environment: PythonEnvironment, + options: PythonTerminalExecutionOptions, +): Promise +``` + +Runs Python in a terminal selected by a stable URI or string key. + +```typescript +await api.runInDedicatedTerminal( + document.uri, + environment, + { + cwd: project.uri, + args: [document.uri.fsPath], + show: true, + }, +); +``` + +`runInDedicatedTerminal()` accepts a `Uri` or string key. Reuse a stable key for +work that should use the same dedicated terminal. + +### `runAsTask` + +```typescript +runAsTask( + environment: PythonEnvironment, + options: PythonTaskExecutionOptions, +): Promise +``` + +`PythonTaskExecutionOptions` requires `name` and `args`; it optionally accepts a +`project`, `cwd`, and string-valued `env`. + +```typescript +const execution = await api.runAsTask(environment, { + name: 'Run tests', + args: ['-m', 'pytest', '-q'], + project, + cwd: project.uri.fsPath, + env: { + PYTHONUNBUFFERED: '1', + }, +}); +``` + +### `runInBackground` + +```typescript +runInBackground( + environment: PythonEnvironment, + options: PythonBackgroundRunOptions, +): Promise +``` + +Starts a new process. `PythonBackgroundRunOptions` requires `args`; `cwd` and +`env` are optional. + +```typescript +const process = await api.runInBackground(environment, { + args: ['-m', 'http.server', '8000'], + cwd: project.uri.fsPath, + env: { + PYTHONUNBUFFERED: '1', + }, +}); + +process.stdout.on('data', (data) => { + output.append(data.toString()); +}); + +process.stderr.on('data', (data) => { + output.append(data.toString()); +}); + +process.onExit((code, signal) => { + output.appendLine(`Python exited: code=${code}, signal=${signal}`); +}); +``` + +`PythonProcess` exposes `pid`, `stdin`, `stdout`, `stderr`, `kill()`, and +`onExit()`. Unlike a VS Code `Event`, `onExit()` does not return a `Disposable`. + +## Environment variable methods + +### `getEnvironmentVariables` + +```typescript +getEnvironmentVariables( + uri: Uri | undefined, + overrides?: ({ [key: string]: string | undefined } | Uri)[], + baseEnvVar?: { [key: string]: string | undefined }, +): Promise<{ [key: string]: string | undefined }> +``` + +`getEnvironmentVariables()` combines process, configured, project, and caller +variables: + +```typescript +const variables = await api.getEnvironmentVariables( + project.uri, + [ + commonEnvironmentFileUri, + { MY_EXTENSION_MODE: 'analysis' }, + ], + { + PATH: process.env.PATH, + PYTHONUTF8: '1', + }, +); +``` + +Values are applied from lowest to highest precedence: + +1. `baseEnvVar`, or `process.env` when it is omitted. +2. The file configured by the `python.envFile` setting. +3. The `.env` file at the Python project root. +4. Each `overrides` entry in array order. + +An override can be a URI for an environment file or an object whose values are +`string | undefined`. Pass `undefined` as the first argument for global scope. + +### `onDidChangeEnvironmentVariables` + +```typescript +onDidChangeEnvironmentVariables: + Event +``` + +Subscribe to changes when cached results depend on these variables: + +```typescript +context.subscriptions.push( + api.onDidChangeEnvironmentVariables((event) => { + const changedFile = event.uri; + const changeType = event.changeType; + }), +); +``` + +The URI is absent when a non-file source changes. `changeType` is VS Code's +`FileChangeType`. + +## Provider methods + +### `registerEnvironmentManager` + +```typescript +registerEnvironmentManager( + manager: EnvironmentManager, + options?: { extensionId?: string }, +): Disposable +``` + +Registers an environment manager and returns a disposable that unregisters it. +When `extensionId` is omitted, or cannot be found, the API attempts to detect +the calling extension. + +```typescript +context.subscriptions.push( + api.registerEnvironmentManager(manager, { + extensionId: context.extension.id, + }), +); +``` + +#### `EnvironmentManager` + +An `EnvironmentManager` discovers environments, controls environment +selection, and can optionally create and remove environments. + +| Member | Required | Purpose | +| --- | --- | --- | +| `name` | Yes | Provider-local ID containing only letters, numbers, `-`, and `_`. | +| `preferredPackageManagerId` | Yes | Fully qualified ID of the preferred package manager. | +| `refresh(scope)` | Yes | Re-discover environments for the scope. | +| `getEnvironments(scope)` | Yes | Return environments known in the scope. | +| `set(scope, environment?)` | Yes | Apply or clear the selected environment. | +| `get(scope)` | Yes | Return the selected environment. | +| `resolve(context)` | Yes | Resolve a URI to an environment or return `undefined`. | +| `create(scope, options?)` | No | Create an environment. | +| `remove(environment, options?)` | No | Remove an environment. | +| `quickCreateConfig()` | No | Describe the manager's quick-create option. | +| `clearCache()` | No | Clear provider-owned environment caches. | +| `onDidChangeEnvironments` | No | Report discovered environment changes. | +| `onDidChangeEnvironment` | No | Report selection changes. | + +Optional metadata includes `displayName`, `description`, `tooltip`, `iconPath`, +and a `LogOutputChannel`. + +Omit unsupported optional methods rather than implementing methods that always +throw. `quickCreateConfig()` enables quick-create UI only when the manager also +implements `create()`. + +### `createPythonEnvironmentItem` + +```typescript +createPythonEnvironmentItem( + info: PythonEnvironmentInfo, + manager: EnvironmentManager, +): PythonEnvironment +``` + +Use `createPythonEnvironmentItem()` rather than constructing `envId`: + +```typescript +const environment = api.createPythonEnvironmentItem( + { + name: discovered.name, + displayName: discovered.displayName, + displayPath: discovered.executable.fsPath, + version: discovered.version, + environmentPath: discovered.executable, + sysPrefix: discovered.sysPrefix, + execInfo: { + run: { + executable: discovered.executable.fsPath, + }, + }, + }, + manager, +); +``` + +`PythonEnvironmentInfo` requires complete execution information and +`sysPrefix`. Register the manager before publishing items created for it. + +The manager may be called by startup, UI, terminal, execution, and other +extension workflows. Implementations should: + +- make `get()` and `getEnvironments()` efficient and safe to call repeatedly; +- update internal state before firing change events; +- return `undefined` from `resolve()` when the URI is not recognized; +- return complete execution details for resolved environments; +- treat `refresh()` as an explicit request to rediscover state; +- clear provider-owned state when `clearCache()` is called; and +- dispose their own event emitters, watchers, processes, and output channels. + +### `registerPackageManager` + +```typescript +registerPackageManager( + manager: PackageManager, + options?: { extensionId?: string }, +): Disposable +``` + +Registers a package manager and returns a disposable that unregisters it. + +```typescript +context.subscriptions.push( + api.registerPackageManager(packageManager, { + extensionId: context.extension.id, + }), +); +``` + +Set an environment manager's `preferredPackageManagerId` to the fully qualified +ID of the package manager intended to handle its environments. + +#### `PackageManager` + +A `PackageManager` reports installed packages and performs package operations +for environments. + +| Member | Required | Purpose | +| --- | --- | --- | +| `name` | Yes | Provider-local ID containing supported manager-name characters. | +| `manage(environment, options)` | Yes | Install or uninstall packages. | +| `refresh(environment)` | Yes | Refresh package state. | +| `getPackages(environment, options?)` | Yes | Return installed packages or `undefined`. | +| `getPackageWatchTargets(environment)` | No | Add manager-specific filesystem watch patterns. | +| `getDirectPackageNames(environment)` | No | Return a best-effort set of direct package names. | +| `clearCache()` | No | Clear provider-owned package caches. | +| `getVersion(environment)` | No | Return the package tool's PEP 440 version. | +| `getPackageAvailableVersions(environment, name)` | No | Return available versions, newest first. | +| `formatInstallSpec(name, version)` | No | Format a versioned install requirement. | +| `onDidChangePackages` | No | Report package additions and removals. | + +Optional metadata includes `displayName`, `description`, `tooltip`, `iconPath`, +and a `LogOutputChannel`. + +When `formatInstallSpec()` is absent, callers should use `name==version`. +`getDirectPackageNames()` is best effort because many package tools cannot +distinguish explicit installation intent from packages with no installed +dependents. + +### `createPackageItem` + +```typescript +createPackageItem( + info: PackageInfo, + environment: PythonEnvironment, + manager: PackageManager, +): Package +``` + +```typescript +const packageItem = api.createPackageItem( + { + name: discovered.name, + displayName: discovered.displayName, + version: discovered.version, + isTransitive: discovered.isTransitive, + }, + environment, + packageManager, +); +``` + +Use this helper instead of constructing `pkgId`. Register the package manager +before creating its items. + +#### Implementing version lookup + +A version lookup implementation should: + +- return `Pep440Version[]` in newest-first order on success; +- throw `PackageVersionLookupNotSupportedError` when the capability is + unsupported; and +- propagate command, network, and parsing failures unchanged. + +Returning `undefined` is allowed by the provider signature, but callers treat +it as an unsupported capability. + +### `registerPythonProjectCreator` + +```typescript +registerPythonProjectCreator( + creator: PythonProjectCreator, +): Disposable +``` + +Registers a project creation workflow and returns a disposable that unregisters +it. + +A `PythonProjectCreator` contributes a project creation workflow. + +| Member | Required | Purpose | +| --- | --- | --- | +| `name` | Yes | Identify the creator. | +| `create(options?)` | Yes | Create projects or standalone files. | +| `displayName` | No | Provide a user-facing name. | +| `description` | No | Describe the creator. | +| `tooltip` | No | Provide additional UI detail. | +| `supportsQuickCreate` | No | Declare support for creation without user input. | + +`create()` returns: + +- `PythonProject` or `PythonProject[]` for created projects; +- `Uri` or `Uri[]` for created files that are not projects; or +- `undefined` when creation produces no result. + +When supplied, `PythonProjectCreatorOptions` contains a required project +`name`, a required `rootUri`, and an optional `quickCreate` flag. + +```typescript +import * as vscode from 'vscode'; +import { + PythonProject, + PythonProjectCreator, + PythonProjectCreatorOptions, +} from '@vscode/python-environments'; + +class ExampleProjectCreator implements PythonProjectCreator { + public readonly name = 'example'; + public readonly displayName = 'Example project'; + public readonly supportsQuickCreate = true; + + public async create( + options?: PythonProjectCreatorOptions, + ): Promise { + if (options === undefined) { + return undefined; + } + + const uri = vscode.Uri.joinPath(options.rootUri, options.name); + await vscode.workspace.fs.createDirectory(uri); + + return { + name: options.name, + uri, + }; + } +} +``` + +Register the creator and retain its disposable: + +```typescript +context.subscriptions.push( + api.registerPythonProjectCreator(projectCreator), +); +``` + +## Errors and lifecycle + +### Events + +API events follow the VS Code `Event` pattern. Store their disposables: + +```typescript +context.subscriptions.push( + api.onDidChangeEnvironments((changes) => { + for (const change of changes) { + output.appendLine( + `${change.kind}: ${change.environment.displayName}`, + ); + } + }), + api.onDidChangeEnvironment(({ uri, old, new: current }) => { + // React to a selected environment change. + }), + api.onDidChangePackages(({ environment, manager, changes }) => { + // React to installed package changes. + }), + api.onDidChangePythonProjects(({ added, removed }) => { + // React to project collection changes. + }), +); +``` + +Change kinds are string enums: + +- `EnvironmentChangeKind.add` and `EnvironmentChangeKind.remove`; +- `PackageChangeKind.add` and `PackageChangeKind.remove`. + +Registration methods also return `Disposable` objects. Disposing a +registration unregisters that provider. Providers remain responsible for +resources they own. + +### Errors and missing values + +Handle rejected promises separately from `undefined` results: + +- `PythonEnvironments.api()` rejects when the extension or API is unavailable. +- `resolveEnvironment()` returns `undefined` when a URI cannot be resolved. +- `createEnvironment()` can return `undefined` when no environment is created. +- `getEnvironment()` returns `undefined` when no environment is selected. +- `getPackages()` can return `undefined` when packages are unavailable. +- legacy package version lookup returns `undefined` for unsupported lookup and + operational failure; +- throw-mode package version lookup distinguishes unsupported capability from + other failures. + +Provider implementations should propagate operational failures rather than +turning them into successful-looking empty results unless the public contract +explicitly defines such a result. + +## Shared types + +### UI-related types + +`IconPath` can be a `Uri`, a light/dark pair of URIs, or a VS Code +`ThemeIcon`. + +`EnvironmentGroupInfo` contains a required group `name` and optional +`description`, `tooltip`, and `iconPath`. + +`QuickCreateConfig` contains a required `description` and optional `detail`. + +## Compatibility guidance + +- Use only exports from `@vscode/python-environments`; do not import + `internal.api.ts` or extension implementation modules. +- Declare `ms-python.vscode-python-envs` in `extensionDependencies`. +- Acquire the API through `PythonEnvironments.api()`. +- Remember that the API object is flat. +- Use `env.envId`, not a direct `env.id`. +- Pass URIs so the API can route project and environment operations. +- Feature-detect optional provider methods. +- Dispose event subscriptions and provider registrations. +- Preserve operational errors and handle documented `undefined` results. +- Use `isPackageVersionLookupNotSupportedError()` across bundle boundaries. +- Recompile after updating the npm package so TypeScript detects API changes. + +The public API is intended to avoid breaking changes. Check +[`api/CHANGELOG.md`](../api/CHANGELOG.md) when updating the package. + +## Related documentation + +- [`src/api.ts`](../src/api.ts) - authoritative API declarations +- [`api/README.md`](../api/README.md) - npm package quick start +- [Making and Managing Python Projects](managing-python-projects.md) +- [Projects API Reference](projects-api-reference.md) +- [Python Environments API Design](design.md) +- [Startup Flow](startup-flow.md) +- [`examples/sample1`](../examples/sample1) - sample environment manager From 5e276044795621a4ec8c80c9706eaa5fe8499c13 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 18 Sep 2026 10:40:47 -0700 Subject: [PATCH 2/6] docs: add API object reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f79caeb-d789-4eb6-898b-4bddd9d3292a --- docs/README.md | 460 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 452 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index 72fe45e2..0dbe20d3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,7 +29,7 @@ The authoritative API declarations are in [`src/api.ts`](../src/api.ts). - [Environment variable methods](#environment-variable-methods) - [Provider methods](#provider-methods) - [Errors and lifecycle](#errors-and-lifecycle) -- [Shared types](#shared-types) +- [Object reference](#object-reference) - [Compatibility guidance](#compatibility-guidance) ## Get started @@ -1085,17 +1085,461 @@ Provider implementations should propagate operational failures rather than turning them into successful-looking empty results unless the public contract explicitly defines such a result. -## Shared types +## Object reference -### UI-related types +This section collects the objects, options, event payloads, and type aliases +referenced by the methods above. Provider interfaces are documented with their +registration methods in [Provider methods](#provider-methods). -`IconPath` can be a `Uri`, a light/dark pair of URIs, or a VS Code -`ThemeIcon`. +### Environment objects -`EnvironmentGroupInfo` contains a required group `name` and optional -`description`, `tooltip`, and `iconPath`. +#### `PythonEnvironment` -`QuickCreateConfig` contains a required `description` and optional `detail`. +Returned by environment discovery, resolution, selection, and creation methods. +It combines [`PythonEnvironmentInfo`](#pythonenvironmentinfo) with an `envId`. + +```typescript +interface PythonEnvironment extends PythonEnvironmentInfo { + readonly envId: PythonEnvironmentId; +} +``` + +#### `PythonEnvironmentId` + +Uniquely identifies an environment and its owning manager. + +```typescript +interface PythonEnvironmentId { + id: string; + managerId: string; +} +``` + +Use both properties for identity. See +[`PythonEnvironment` and identity](#pythonenvironment-and-identity). + +#### `PythonEnvironmentInfo` + +Describes an environment before the API assigns its `envId`. It is passed to +[`createPythonEnvironmentItem()`](#createpythonenvironmentitem) and forms the +base of every returned `PythonEnvironment`. + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Environment name. | +| `displayName` | `string` | Yes | Primary user-facing name. | +| `displayPath` | `string` | Yes | User-facing path. | +| `version` | `string` | Yes | Python version. | +| `environmentPath` | `Uri` | Yes | Python executable or environment directory. | +| `execInfo` | `PythonEnvironmentExecutionInfo` | Yes | Commands for running and activating Python. | +| `sysPrefix` | `string` | Yes | Value of Python's `sys.prefix`. | +| `shortDisplayName` | `string` | No | Compact user-facing name. | +| `description` | `string` | No | Additional environment description. | +| `tooltip` | `string \| MarkdownString` | No | Hover text. | +| `iconPath` | `IconPath` | No | Environment icon. | +| `group` | `string \| EnvironmentGroupInfo` | No | Environment UI group. | +| `error` | `string` | No | Diagnostic for a broken or invalid environment. | + +#### `PythonCommandRunConfiguration` + +Describes one executable invocation. + +```typescript +interface PythonCommandRunConfiguration { + executable: string; + args?: string[]; +} +``` + +`executable` must be an absolute path to an executable that can be spawned. +`args` are included on every invocation of that command. + +#### `PythonEnvironmentExecutionInfo` + +Describes how to execute, activate, and deactivate an environment. + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `run` | `PythonCommandRunConfiguration` | Yes | Default Python command. | +| `activatedRun` | `PythonCommandRunConfiguration` | No | Python command to use after activation. | +| `activation` | `PythonCommandRunConfiguration[]` | No | Generic activation commands. | +| `shellActivation` | `Map` | No | Activation commands by shell name. | +| `deactivation` | `PythonCommandRunConfiguration[]` | No | Generic deactivation commands. | +| `shellDeactivation` | `Map` | No | Deactivation commands by shell name. | + +The `unknown` map key can provide a fallback when the shell type is not known. + +#### Environment scope aliases + +| Type | Definition | Referenced by | +| --- | --- | --- | +| `GetEnvironmentsScope` | `Uri \| 'all' \| 'global'` | `getEnvironments()` and manager discovery | +| `RefreshEnvironmentsScope` | `Uri \| undefined` | `refreshEnvironments()` and manager refresh | +| `ResolveEnvironmentContext` | `Uri` | `resolveEnvironment()` and manager resolution | +| `GetEnvironmentScope` | `Uri \| undefined` | `getEnvironment()` and manager selection lookup | +| `SetEnvironmentScope` | `Uri \| Uri[] \| undefined` | `setEnvironment()` and manager selection updates | +| `CreateEnvironmentScope` | `Uri \| Uri[] \| 'global'` | `createEnvironment()` and manager creation | + +#### `CreateEnvironmentOptions` + +Passed to [`createEnvironment()`](#createenvironment) and +`EnvironmentManager.create()`. + +```typescript +interface CreateEnvironmentOptions { + quickCreate?: boolean; + additionalPackages?: string[]; +} +``` + +`quickCreate: true` requests creation without input. `false` permits prompts +and records that quick create was skipped. When omitted, prompts are permitted +and the manager may offer quick create. + +#### `RemoveEnvironmentOptions` + +Passed to [`removeEnvironment()`](#removeenvironment) and +`EnvironmentManager.remove()`. + +```typescript +interface RemoveEnvironmentOptions { + runHeadless?: boolean; +} +``` + +When `runHeadless` is true, removal should not prompt for confirmation. + +#### `QuickCreateConfig` + +Returned by an environment manager's optional `quickCreateConfig()` method. + +```typescript +interface QuickCreateConfig { + readonly description: string; + readonly detail?: string; +} +``` + +#### `DidChangeEnvironmentsEventArgs` and `EnvironmentChangeKind` + +`DidChangeEnvironmentsEventArgs` is an array of discovered-environment changes: + +```typescript +type DidChangeEnvironmentsEventArgs = { + kind: EnvironmentChangeKind; + environment: PythonEnvironment; +}[]; + +enum EnvironmentChangeKind { + add = 'add', + remove = 'remove', +} +``` + +`DidChangeEnvironmentEventArgs` describes a selection change: + +```typescript +type DidChangeEnvironmentEventArgs = { + readonly uri: Uri | undefined; + readonly old: PythonEnvironment | undefined; + readonly new: PythonEnvironment | undefined; +}; +``` + +### Package objects + +#### `Package` + +Returned by [`getPackages()`](#getpackages) and supplied in package change +events. It combines [`PackageInfo`](#packageinfo) with a `pkgId`. + +```typescript +interface Package extends PackageInfo { + readonly pkgId: PackageId; +} +``` + +#### `PackageId` + +Identifies a package, its package manager, and its environment. + +```typescript +interface PackageId { + id: string; + managerId: string; + environmentId: string; +} +``` + +#### `PackageInfo` + +Passed to [`createPackageItem()`](#createpackageitem) and forms the base of +every returned `Package`. + +| Property | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Package name. | +| `displayName` | `string` | Yes | User-facing package name. | +| `version` | `string` | No | Installed package version. | +| `description` | `string` | No | Package description. | +| `tooltip` | `string \| MarkdownString` | No | Hover text. | +| `iconPath` | `IconPath` | No | Package icon. | +| `uris` | `readonly Uri[]` | No | Files or locations associated with the package. | +| `isTransitive` | `boolean` | No | Whether the package is a transitive dependency. | + +#### `GetPackagesOptions` + +Passed to [`getPackages()`](#getpackages) and `PackageManager.getPackages()`. + +```typescript +interface GetPackagesOptions { + skipCache?: boolean; +} +``` + +Set `skipCache` to true to request current data from the underlying package +tool. + +#### `PackageManagementOptions` + +Passed to [`managePackages()`](#managepackages) and +`PackageManager.manage()`. At least one of `install` or `uninstall` is required. + +```typescript +type PackageManagementOptions = { + runHeadless?: boolean; + upgrade?: boolean; + showSkipOption?: boolean; + install?: string[]; + uninstall?: string[]; +}; +``` + +The exported type uses a union to enforce the `install` or `uninstall` +requirement at compile time. `PackageManagementInteractionOptions` contributes +the optional `runHeadless` property. + +#### `GetPackageAvailableVersionsOptions` + +Controls error behavior for +[`getPackageAvailableVersions()`](#getpackageavailableversions). + +```typescript +interface GetPackageAvailableVersionsOptions { + errorMode?: 'legacy' | 'throw'; +} +``` + +#### `Pep440Version` + +Represents a parsed PEP 440 package version. It is re-exported from +`@renovatebot/pep440` and returned by package tool/version lookup methods. + +#### `DidChangePackagesEventArgs` and `PackageChangeKind` + +```typescript +interface DidChangePackagesEventArgs { + environment: PythonEnvironment; + manager: PackageManager; + changes: { kind: PackageChangeKind; pkg: Package }[]; +} + +enum PackageChangeKind { + add = 'add', + remove = 'remove', +} +``` + +### Project objects + +#### `PythonProject` + +Returned by project lookup methods and accepted by project modification and +execution methods. + +```typescript +interface PythonProject { + readonly name: string; + readonly uri: Uri; + readonly description?: string; + readonly tooltip?: string | MarkdownString; +} +``` + +#### `PythonProjectCreatorOptions` + +Passed to `PythonProjectCreator.create()`. + +```typescript +interface PythonProjectCreatorOptions { + name: string; + rootUri: Uri; + quickCreate?: boolean; +} +``` + +#### `DidChangePythonProjectsEventArgs` + +Passed to [`onDidChangePythonProjects`](#ondidchangepythonprojects). + +```typescript +interface DidChangePythonProjectsEventArgs { + added: PythonProject[]; + removed: PythonProject[]; +} +``` + +### Execution objects + +#### `PythonTerminalCreateOptions` + +Passed to [`createTerminal()`](#createterminal). It includes all VS Code +`TerminalOptions` and adds: + +```typescript +interface PythonTerminalCreateOptions extends TerminalOptions { + disableActivation?: boolean; +} +``` + +#### `PythonTerminalExecutionOptions` + +Passed to [`runInTerminal()`](#runinterminal) and +[`runInDedicatedTerminal()`](#runindedicatedterminal). + +```typescript +interface PythonTerminalExecutionOptions { + cwd: string | Uri; + args?: string[]; + show?: boolean; +} +``` + +#### `PythonTaskExecutionOptions` + +Passed to [`runAsTask()`](#runastask). + +```typescript +interface PythonTaskExecutionOptions { + name: string; + args: string[]; + project?: PythonProject; + cwd?: string; + env?: { [key: string]: string }; +} +``` + +#### `PythonBackgroundRunOptions` + +Passed to [`runInBackground()`](#runinbackground). + +```typescript +interface PythonBackgroundRunOptions { + args: string[]; + cwd?: string; + env?: { [key: string]: string | undefined }; +} +``` + +#### `PythonProcess` + +Returned by [`runInBackground()`](#runinbackground). + +```typescript +interface PythonProcess { + readonly pid?: number; + readonly stdin: NodeJS.WritableStream; + readonly stdout: NodeJS.ReadableStream; + readonly stderr: NodeJS.ReadableStream; + + kill(): void; + onExit( + listener: ( + code: number | null, + signal: NodeJS.Signals | null, + ) => void, + ): void; +} +``` + +### Environment variable objects + +#### `DidChangeEnvironmentVariablesEventArgs` + +Passed to +[`onDidChangeEnvironmentVariables`](#ondidchangeenvironmentvariables). + +```typescript +interface DidChangeEnvironmentVariablesEventArgs { + uri?: Uri; + changeType: FileChangeType; +} +``` + +`uri` is absent for a non-file source. `changeType` is VS Code's +`FileChangeType`. + +### Shared UI objects + +#### `IconPath` + +Used by environment, package, group, and provider display objects. + +```typescript +type IconPath = + | Uri + | { + light: Uri; + dark: Uri; + } + | ThemeIcon; +``` + +#### `EnvironmentGroupInfo` + +Provides display information for an environment group. + +```typescript +interface EnvironmentGroupInfo { + readonly name: string; + readonly description?: string; + readonly tooltip?: string | MarkdownString; + readonly iconPath?: IconPath; +} +``` + +When several group definitions use the same name, the first instance is used +in the UI. + +### API interface groups + +The interfaces below organize the flat API for type composition. They do not +represent nested runtime objects. + +| Interface | Members grouped by the interface | +| --- | --- | +| `PythonEnvironmentsApi` | Environment discovery and resolution | +| `PythonProjectEnvironmentApi` | Selected environment get/set | +| `PythonEnvironmentManagementApi` | Environment creation/removal | +| `PythonEnvironmentItemApi` | Environment item creation | +| `PythonEnvironmentManagerRegistrationApi` | Environment manager registration | +| `PythonEnvironmentManagerApi` | Combined environment API | +| `PythonPackageGetterApi` | Package retrieval and version lookup | +| `PythonPackageManagementApi` | Package installation/removal | +| `PythonPackageItemApi` | Package item creation | +| `PythonPackageManagerRegistrationApi` | Package manager registration | +| `PythonPackageManagerApi` | Combined package API | +| `PythonProjectGetterApi` | Project lookup | +| `PythonProjectModifyApi` | Project collection modification | +| `PythonProjectCreationApi` | Project creator registration | +| `PythonProjectApi` | Combined project API | +| `PythonTerminalCreateApi` | Terminal creation | +| `PythonTerminalRunApi` | Terminal execution | +| `PythonTaskRunApi` | Task execution | +| `PythonBackgroundRunApi` | Background execution | +| `PythonExecutionApi` | Combined execution API | +| `PythonEnvironmentVariablesApi` | Environment variable lookup/events | +| `PythonEnvironmentApi` | Complete flat public API | ## Compatibility guidance From 427edd5404eb2a3f25810312c8979372c24c0559 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 18 Sep 2026 10:43:38 -0700 Subject: [PATCH 3/6] docs: align API references with public contracts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f79caeb-d789-4eb6-898b-4bddd9d3292a --- docs/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 0dbe20d3..f33dbb8f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,9 @@ Use this manual to: - work with Python projects and environment variables; or - contribute an environment manager, package manager, or project creator. -The authoritative API declarations are in [`src/api.ts`](../src/api.ts). +The runtime facade is [`src/api.ts`](../src/api.ts). The authoritative public +contracts are in [`src/types.ts`](../src/types.ts), with public errors and type +guards in [`src/publicErrors.ts`](../src/publicErrors.ts). > [!IMPORTANT] > The API is flat. Call `api.getEnvironments()`, not @@ -1561,10 +1563,11 @@ The public API is intended to avoid breaking changes. Check ## Related documentation -- [`src/api.ts`](../src/api.ts) - authoritative API declarations +- [`src/api.ts`](../src/api.ts) - runtime API facade +- [`src/types.ts`](../src/types.ts) - authoritative public type contracts +- [`src/publicErrors.ts`](../src/publicErrors.ts) - public errors and type guards - [`api/README.md`](../api/README.md) - npm package quick start - [Making and Managing Python Projects](managing-python-projects.md) - [Projects API Reference](projects-api-reference.md) - [Python Environments API Design](design.md) - [Startup Flow](startup-flow.md) -- [`examples/sample1`](../examples/sample1) - sample environment manager From 6ec84a2fc0eed7bf88ca2f2113c57da463c2a4e6 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 18 Sep 2026 10:56:04 -0700 Subject: [PATCH 4/6] docs: describe API interface groups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f79caeb-d789-4eb6-898b-4bddd9d3292a --- docs/README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/README.md b/docs/README.md index f33dbb8f..6f7d60ae 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1518,30 +1518,30 @@ in the UI. The interfaces below organize the flat API for type composition. They do not represent nested runtime objects. -| Interface | Members grouped by the interface | -| --- | --- | -| `PythonEnvironmentsApi` | Environment discovery and resolution | -| `PythonProjectEnvironmentApi` | Selected environment get/set | -| `PythonEnvironmentManagementApi` | Environment creation/removal | -| `PythonEnvironmentItemApi` | Environment item creation | -| `PythonEnvironmentManagerRegistrationApi` | Environment manager registration | -| `PythonEnvironmentManagerApi` | Combined environment API | -| `PythonPackageGetterApi` | Package retrieval and version lookup | -| `PythonPackageManagementApi` | Package installation/removal | -| `PythonPackageItemApi` | Package item creation | -| `PythonPackageManagerRegistrationApi` | Package manager registration | -| `PythonPackageManagerApi` | Combined package API | -| `PythonProjectGetterApi` | Project lookup | -| `PythonProjectModifyApi` | Project collection modification | -| `PythonProjectCreationApi` | Project creator registration | -| `PythonProjectApi` | Combined project API | -| `PythonTerminalCreateApi` | Terminal creation | -| `PythonTerminalRunApi` | Terminal execution | -| `PythonTaskRunApi` | Task execution | -| `PythonBackgroundRunApi` | Background execution | -| `PythonExecutionApi` | Combined execution API | -| `PythonEnvironmentVariablesApi` | Environment variable lookup/events | -| `PythonEnvironmentApi` | Complete flat public API | +| Interface | Members grouped by the interface | Description | +| --- | --- | --- | +| `PythonEnvironmentsApi` | Environment discovery and resolution | Lists and refreshes discovered environments, resolves environment URIs, and reports discovery changes. | +| `PythonProjectEnvironmentApi` | Selected environment get/set | Reads, updates, and observes the selected environment for URI or global scopes. | +| `PythonEnvironmentManagementApi` | Environment creation/removal | Creates and removes environments through their associated environment managers. | +| `PythonEnvironmentItemApi` | Environment item creation | Converts provider-supplied environment information into an identified `PythonEnvironment`. | +| `PythonEnvironmentManagerRegistrationApi` | Environment manager registration | Registers an `EnvironmentManager` implementation with the extension. | +| `PythonEnvironmentManagerApi` | Combined environment API | Combines environment registration, item creation, lifecycle, discovery, and selection interfaces. | +| `PythonPackageGetterApi` | Package retrieval and version lookup | Retrieves and refreshes packages, looks up available versions, and reports package changes. | +| `PythonPackageManagementApi` | Package installation/removal | Installs, upgrades, or uninstalls packages in an environment. | +| `PythonPackageItemApi` | Package item creation | Converts provider-supplied package information into an identified `Package`. | +| `PythonPackageManagerRegistrationApi` | Package manager registration | Registers a `PackageManager` implementation with the extension. | +| `PythonPackageManagerApi` | Combined package API | Combines package registration, retrieval, management, and item creation interfaces. | +| `PythonProjectGetterApi` | Project lookup | Returns all known projects or the project associated with a URI. | +| `PythonProjectModifyApi` | Project collection modification | Adds, removes, and observes projects in the tracked project collection. | +| `PythonProjectCreationApi` | Project creator registration | Registers a `PythonProjectCreator` implementation. | +| `PythonProjectApi` | Combined project API | Combines project lookup, modification, events, and creator registration interfaces. | +| `PythonTerminalCreateApi` | Terminal creation | Creates a terminal configured for a Python environment. | +| `PythonTerminalRunApi` | Terminal execution | Runs Python in shared or dedicated terminals. | +| `PythonTaskRunApi` | Task execution | Runs Python as a VS Code task. | +| `PythonBackgroundRunApi` | Background execution | Starts Python as a background process with stream and exit access. | +| `PythonExecutionApi` | Combined execution API | Combines terminal creation, terminal execution, task execution, and background execution. | +| `PythonEnvironmentVariablesApi` | Environment variable lookup/events | Resolves effective environment variables and reports source changes. | +| `PythonEnvironmentApi` | Complete flat public API | Combines all environment, package, project, execution, and environment-variable APIs exposed at runtime. | ## Compatibility guidance From 6a5e0ea1403873f18c6bed02f075d0ecbbfb62b1 Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 18 Sep 2026 12:29:21 -0700 Subject: [PATCH 5/6] docs: restructure API manual by domain Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f79caeb-d789-4eb6-898b-4bddd9d3292a --- docs/README.md | 2201 ++++++++++++++++++++++++++---------------------- 1 file changed, 1204 insertions(+), 997 deletions(-) diff --git a/docs/README.md b/docs/README.md index 6f7d60ae..e98e3caf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,8 +8,9 @@ Use this manual to: - discover, select, create, and remove Python environments; - inspect and manage installed packages; +- work with Python projects; - run Python in terminals, tasks, or background processes; -- work with Python projects and environment variables; or +- resolve environment variables; or - contribute an environment manager, package manager, or project creator. The runtime facade is [`src/api.ts`](../src/api.ts). The authoritative public @@ -23,18 +24,32 @@ guards in [`src/publicErrors.ts`](../src/publicErrors.ts). ## Contents -- [Get started](#get-started) -- [Environment methods](#environment-methods) -- [Package methods](#package-methods) -- [Project methods](#project-methods) -- [Execution methods](#execution-methods) -- [Environment variable methods](#environment-variable-methods) -- [Provider methods](#provider-methods) -- [Errors and lifecycle](#errors-and-lifecycle) -- [Object reference](#object-reference) +- [Getting started](#getting-started) +- [Domains](#domains) +- [Environments](#environments) + - [Environment data types](#environment-data-types) + - [Environment methods](#environment-methods) +- [Packages](#packages) + - [Package data types](#package-data-types) + - [Package methods](#package-methods) + - [Package errors](#package-errors) +- [Projects](#projects) + - [Project data types](#project-data-types) + - [Project methods](#project-methods) +- [Execution](#execution) + - [Execution data types](#execution-data-types) + - [Execution methods](#execution-methods) +- [Environment variables](#environment-variables) + - [Environment variable data types](#environment-variable-data-types) + - [Environment variable methods](#environment-variable-methods) +- [Extensibility](#extensibility) + - [Extensibility data types](#extensibility-data-types) + - [Extensibility methods](#extensibility-methods) +- [API interface groups](#api-interface-groups) - [Compatibility guidance](#compatibility-guidance) +- [Related documentation](#related-documentation) -## Get started +## Getting started ### Install the package @@ -56,7 +71,7 @@ The npm package provides the public types and the helper used to acquire the API. The Python Environments VS Code extension provides the implementation at runtime. -### `PythonEnvironments.api` +### Acquire the API Call `PythonEnvironments.api()` during activation: @@ -79,1444 +94,1636 @@ export async function activate( `PythonEnvironments.api()`: -1. Finds the extension with ID `ms-python.vscode-python-envs`. +1. Finds the extension with ID `ms-python.vscode-python-envs` + (exported as `EXTENSION_ID`). 2. Activates it when necessary. 3. Returns its `PythonEnvironmentApi`. It rejects when the extension is missing or disabled, activation fails, or the extension does not expose its API. Acquire the object once and reuse it. -### API areas +### How to read this manual -`PythonEnvironmentApi` combines the following interfaces into one object: +Each domain below is self-contained and has the same shape: -| Area | Representative members | -| --- | --- | -| Environments | `getEnvironments`, `resolveEnvironment`, `setEnvironment`, `createEnvironment` | -| Packages | `getPackages`, `managePackages`, `getPackageAvailableVersions` | -| Projects | `getPythonProjects`, `getPythonProject`, `addPythonProject` | -| Execution | `createTerminal`, `runInTerminal`, `runAsTask`, `runInBackground` | -| Environment variables | `getEnvironmentVariables`, `onDidChangeEnvironmentVariables` | -| Provider registration | `registerEnvironmentManager`, `registerPackageManager`, `registerPythonProjectCreator` | +1. **Data types** - the objects the domain accepts and returns. Every type is + documented as a field table with these columns: + + | Column | Meaning | + | --- | --- | + | Field | The property name as declared in `src/types.ts`. | + | Type | The TypeScript type of the property. | + | Required | `Yes` when the property must be present; `No` when it is optional (`?`). | + | Description | What the property means and how the extension uses it. | -## Environment methods + Most returned objects declare their properties `readonly`. Treat everything + the API hands back as immutable, and build new objects rather than mutating + them. -Environment methods discover, resolve, create, remove, and select Python -environments. +2. **Methods** - the calls the domain exposes. Each method documents its + signature, a parameter table using the same `Required` convention, its + return type, and an example. -### Scopes +Locations are not all the same type. Scopes, project locations, and resolution +contexts are declared as `vscode.Uri`, so pass a `Uri` there rather than a +string, and let the extension resolve the owning project. A few options carry a +filesystem path instead - `PythonTaskExecutionOptions.cwd`, +`PythonBackgroundRunOptions.cwd`, and `PythonCommandRunConfiguration.executable` +are all `string`. Use `uri.fsPath` for those so the separator is correct on +every platform. -Many API calls use a scope to identify a project, workspace, manager, or global -state. Pass a `vscode.Uri` rather than a filesystem path whenever a method -accepts a URI. +## Domains -| Type | Values | Used by | +| Domain | Use it to | Representative members | | --- | --- | --- | -| `GetEnvironmentsScope` | `Uri`, `'all'`, or `'global'` | `getEnvironments` | -| `RefreshEnvironmentsScope` | `Uri` or `undefined` | `refreshEnvironments` | -| `GetEnvironmentScope` | `Uri` or `undefined` | `getEnvironment` | -| `SetEnvironmentScope` | `Uri`, `Uri[]`, or `undefined` | `setEnvironment` | -| `CreateEnvironmentScope` | `Uri`, `Uri[]`, or `'global'` | `createEnvironment` | -| `ResolveEnvironmentContext` | `Uri` | `resolveEnvironment` | +| [Environments](#environments) | Discover, resolve, select, create, and remove interpreters. | `getEnvironments`, `resolveEnvironment`, `setEnvironment`, `createEnvironment` | +| [Packages](#packages) | Read and change what is installed in an environment. | `getPackages`, `managePackages`, `getPackageAvailableVersions` | +| [Projects](#projects) | Read and modify the set of Python projects. | `getPythonProjects`, `getPythonProject`, `addPythonProject` | +| [Execution](#execution) | Run Python with an environment already activated. | `createTerminal`, `runInTerminal`, `runAsTask`, `runInBackground` | +| [Environment variables](#environment-variables) | Resolve the effective variables for a scope. | `getEnvironmentVariables`, `onDidChangeEnvironmentVariables` | +| [Extensibility](#extensibility) | Contribute your own managers and creators. | `registerEnvironmentManager`, `registerPackageManager`, `registerPythonProjectCreator` | -The scope values mean: +## Environments -- **`Uri`**: the workspace, folder, file, environment directory, or Python - executable relevant to that method. -- **`Uri[]`**: apply an environment operation to multiple URI scopes. -- **`'all'`**: return all discovered environments. -- **`'global'`**: address global Python installations or global environment - creation. -- **`undefined` for selection**: get or set the global environment selection. -- **`undefined` for refresh**: refresh global and workspace discovery. -- **`undefined` for environment variables**: resolve variables for global - scope. +An environment is a Python interpreter plus the information needed to run and +activate it. Environments are supplied by environment managers (venv, conda, +and any manager contributed by another extension) and are identified by +`envId`, never by path alone. -`getEnvironments()` does not accept `undefined`. Use a URI, `'all'`, or -`'global'`. +### Environment data types -`resolveEnvironment()` currently accepts only a `Uri`. Use the exported -signature as the contract even though older documentation may describe other -inputs. +#### `PythonEnvironmentId` -### `PythonEnvironment` and identity +Uniquely identifies an environment. Two environments are the same only when +both fields match. -A `PythonEnvironment` contains an `envId`: +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `id` | `string` | Yes | Manager-scoped unique identifier for the environment. Unique only within `managerId`. | +| `managerId` | `string` | Yes | Identifier of the environment manager that owns the environment, formatted `.:`. | ```typescript -interface PythonEnvironmentId { - id: string; - managerId: string; -} +const key = `${env.envId.managerId}:${env.envId.id}`; ``` -- `envId.id` identifies the environment within its manager. -- `envId.managerId` identifies the manager that owns the environment. - -Use both values when storing or comparing identities: +#### `PythonEnvironmentInfo` -```typescript -import { PythonEnvironment } from '@vscode/python-environments'; +The descriptive payload of an environment. Providers build this object and pass +it to [`createPythonEnvironmentItem`](#createpythonenvironmentitem); consumers +read these fields off a `PythonEnvironment`. -function environmentKey(environment: PythonEnvironment): string { - return `${environment.envId.managerId}:${environment.envId.id}`; -} +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Short internal name of the environment. | +| `displayName` | `string` | Yes | Name shown in pickers and the environment tree. | +| `shortDisplayName` | `string` | No | Compact name for constrained UI such as the status bar. | +| `displayPath` | `string` | Yes | Human-readable path shown alongside the name. Use a home-relative or otherwise shortened form. | +| `version` | `string` | Yes | Python version string, for example `3.12.1`. | +| `environmentPath` | `Uri` | Yes | Path to the Python binary or the environment folder. | +| `description` | `string` | No | Extra descriptive text shown next to the environment. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the environment. | +| `iconPath` | [`IconPath`](#iconpath) | No | Icon shown for the environment. | +| `execInfo` | [`PythonEnvironmentExecutionInfo`](#pythonenvironmentexecutioninfo) | Yes | How to run and activate the interpreter. Required for any execution. | +| `sysPrefix` | `string` | Yes | Value of `sys.prefix` for the environment. Consumed by Pylance, Jupyter, and similar extensions. | +| `group` | `string \| EnvironmentGroupInfo` | No | Groups the environment in the environment manager UI. The first group instance with a given name wins. | +| `error` | `string` | No | Set when the environment is broken or invalid, for example a missing interpreter or dangling symlink. The UI shows a warning with this message. | + +```typescript +const info: PythonEnvironmentInfo = { + name: 'my-venv', + displayName: 'Python 3.12.1 (my-venv)', + shortDisplayName: 'my-venv', + displayPath: '~/code/app/.venv', + version: '3.12.1', + environmentPath: vscode.Uri.file('/home/me/code/app/.venv/bin/python'), + sysPrefix: '/home/me/code/app/.venv', + execInfo: { run: { executable: '/home/me/code/app/.venv/bin/python' } }, +}; ``` -Do not read `environment.id`; the identifier is `environment.envId`. +#### `PythonEnvironment` -### `getEnvironments` +`PythonEnvironmentInfo` plus its identity. This is the object every environment +method returns and nearly every other method accepts. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `envId` | [`PythonEnvironmentId`](#pythonenvironmentid) | Yes | Identity of the environment. Use this - there is no `id` property. | +| *(inherited)* | [`PythonEnvironmentInfo`](#pythonenvironmentinfo) | - | All descriptive fields listed above. | ```typescript -getEnvironments( - scope: Uri | 'all' | 'global', -): Promise +const env = await api.getEnvironment(projectUri); +if (env) { + console.log(env.displayName, env.envId.id, env.execInfo.run.executable); +} ``` -Returns environments associated with a URI, global Python installations, or -all discovered environments. The result is always an array. +> [!NOTE] +> `PythonEnvironment` is a structural interface, so TypeScript will accept a +> hand-written literal - but manager-backed calls such as `getPackages` and +> `removeEnvironment` need an `envId` that belongs to a registered manager. +> Consumers should pass environments the API returned; providers should build +> them with `createPythonEnvironmentItem`, which attaches a valid `envId`. + +#### `EnvironmentGroupInfo` + +Describes a group heading in the environment manager UI, for managers that want +richer grouping than a plain string. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Group name, also used as the group identifier. | +| `description` | `string` | No | Secondary text for the group. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the group. | +| `iconPath` | [`IconPath`](#iconpath) | No | Icon shown for the group. | ```typescript -const all = await api.getEnvironments('all'); -const globalInstallations = await api.getEnvironments('global'); -const projectEnvironments = await api.getEnvironments(projectUri); +const group: EnvironmentGroupInfo = { + name: 'Conda', + description: 'Managed by conda', + iconPath: new vscode.ThemeIcon('package'), +}; ``` -Each result contains the following `PythonEnvironmentInfo` plus `envId`: +#### `IconPath` -| Property | Type | Required | -| --- | --- | --- | -| `name` | `string` | Yes | -| `displayName` | `string` | Yes | -| `displayPath` | `string` | Yes | -| `version` | `string` | Yes | -| `environmentPath` | `Uri` | Yes | -| `execInfo` | `PythonEnvironmentExecutionInfo` | Yes | -| `sysPrefix` | `string` | Yes | -| `shortDisplayName` | `string` | No | -| `description` | `string` | No | -| `tooltip` | `string \| MarkdownString` | No | -| `iconPath` | `IconPath` | No | -| `group` | `string \| EnvironmentGroupInfo` | No | -| `error` | `string` | No | +Shared icon type used by environments, groups, packages, managers, and +creators. -### `refreshEnvironments` +| Form | Description | +| --- | --- | +| `Uri` | A single icon used for every theme. | +| `{ light: Uri; dark: Uri }` | Theme-specific icons; both fields are required. | +| `ThemeIcon` | A built-in VS Code codicon, for example `new vscode.ThemeIcon('snake')`. | -```typescript -refreshEnvironments(scope: Uri | undefined): Promise -``` +#### Scope types -Refreshes discovery for a URI. Pass `undefined` to refresh global and workspace -discovery. +Scopes tell the extension *which* project, folder, or global state a call +applies to. -```typescript -await api.refreshEnvironments(projectUri); -await api.refreshEnvironments(undefined); -``` +| Type | Values | Meaning | Used by | +| --- | --- | --- | --- | +| `GetEnvironmentsScope` | `Uri \| 'all' \| 'global'` | `Uri` limits results to the owning project; `'all'` returns everything known; `'global'` returns base installations used to create virtual environments. | [`getEnvironments`](#getenvironments) | +| `RefreshEnvironmentsScope` | `Uri \| undefined` | `Uri` refreshes one project; `undefined` refreshes global and workspace discovery. | [`refreshEnvironments`](#refreshenvironments) | +| `GetEnvironmentScope` | `Uri \| undefined` | `Uri` reads the selection for that project or file; `undefined` reads the global selection. | [`getEnvironment`](#getenvironment) | +| `SetEnvironmentScope` | `Uri \| Uri[] \| undefined` | `Uri` or `Uri[]` sets the selection for those projects; `undefined` sets the global selection. | [`setEnvironment`](#setenvironment) | +| `CreateEnvironmentScope` | `Uri \| Uri[] \| 'global'` | Where to create the environment; `'global'` creates one outside any project. | [`createEnvironment`](#createenvironment) | +| `ResolveEnvironmentContext` | `Uri` | An interpreter path or environment folder to resolve. Only a `Uri` is accepted. | [`resolveEnvironment`](#resolveenvironment) | -### `resolveEnvironment` +#### `CreateEnvironmentOptions` + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `quickCreate` | `boolean` | No | `true` creates without any prompts. `false` means the user explicitly declined quick create, so prompts are allowed. `undefined` leaves the decision to the manager, which may offer quick create. | +| `additionalPackages` | `string[]` | No | Packages to install in addition to whatever the manager installs by default. | ```typescript -resolveEnvironment( - context: Uri, -): Promise +const env = await api.createEnvironment(projectUri, { + quickCreate: true, + additionalPackages: ['requests', 'pytest'], +}); ``` -Resolves an environment directory or Python executable URI. It returns -`undefined` when no manager can resolve the URI. The current exported signature -accepts only `Uri`. +#### `RemoveEnvironmentOptions` -```typescript -const resolved = await api.resolveEnvironment(pythonExecutableUri); -if (resolved === undefined) { - // No registered manager resolved the URI. -} -``` +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `runHeadless` | `boolean` | No | `true` removes the environment without a confirmation prompt. Intended for automated scenarios. Defaults to `false`. | -### `getEnvironment` +#### `QuickCreateConfig` -```typescript -getEnvironment( - scope: Uri | undefined, -): Promise -``` +Returned by an environment manager's `quickCreateConfig()` to describe its +one-click creation path. Returning `undefined` disables quick create. -Gets the selected environment for a URI. Pass `undefined` for the global -selection. It returns `undefined` when no environment is selected. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `description` | `string` | Yes | Short label for the quick create step. | +| `detail` | `string` | No | Secondary text explaining what quick create will do. | ```typescript -const selected = await api.getEnvironment(projectUri); -const globalSelection = await api.getEnvironment(undefined); +quickCreateConfig(): QuickCreateConfig | undefined { + return { description: 'Quick create', detail: 'Creates .venv with pip' }; +} ``` -### `setEnvironment` +#### `EnvironmentChangeKind` -```typescript -setEnvironment( - scope: Uri | Uri[] | undefined, - environment?: PythonEnvironment, -): Promise -``` +String enum describing a discovery change. -Sets the selected environment for one or more URI scopes, or for global scope -when `scope` is `undefined`. Omit `environment` to clear the selection. +| Member | Value | Description | +| --- | --- | --- | +| `EnvironmentChangeKind.add` | `'add'` | An environment became known. | +| `EnvironmentChangeKind.remove` | `'remove'` | An environment is no longer known. | -```typescript -await api.setEnvironment(projectUri, environment); -await api.setEnvironment([applicationUri, testsUri], environment); -await api.setEnvironment(projectUri, undefined); -``` +#### `DidChangeEnvironmentsEventArgs` -### `createEnvironment` +Payload of [`onDidChangeEnvironments`](#ondidchangeenvironments). This is an +**array**; each element describes one discovery change. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `kind` | [`EnvironmentChangeKind`](#environmentchangekind) | Yes | Whether the environment was added or removed. | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment that was added or removed. | ```typescript -createEnvironment( - scope: Uri | Uri[] | 'global', - options?: CreateEnvironmentOptions, -): Promise +api.onDidChangeEnvironments((changes) => { + for (const { kind, environment } of changes) { + console.log(kind, environment.displayName); + } +}); ``` -Creates an environment through the manager associated with the scope. It -returns `undefined` when no environment is created. +#### `DidChangeEnvironmentEventArgs` -```typescript -const environment = await api.createEnvironment(projectUri, { - quickCreate: true, - additionalPackages: ['pytest'], -}); +Payload of [`onDidChangeEnvironment`](#ondidchangeenvironment), fired when the +*selected* environment changes. -if (environment !== undefined) { - await api.setEnvironment(projectUri, environment); -} -``` +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `uri` | `Uri \| undefined` | Yes (may be `undefined`) | The scope whose selection changed. `undefined` means the global selection. | +| `old` | `PythonEnvironment \| undefined` | Yes (may be `undefined`) | The previously selected environment, or `undefined` if nothing was selected. | +| `new` | `PythonEnvironment \| undefined` | Yes (may be `undefined`) | The newly selected environment, or `undefined` if the selection was cleared. | -`CreateEnvironmentOptions` supports: +### Environment methods -| Property | Meaning | -| --- | --- | -| `quickCreate: true` | Request creation without user input or prompts. | -| `quickCreate: false` | Permit prompts and indicate that quick create was explicitly skipped. | -| `quickCreate: undefined` | Permit prompts and allow the manager to offer quick create. | -| `additionalPackages` | Install these packages in addition to packages chosen during creation. | +#### `getEnvironments` -### `removeEnvironment` +Returns the environments currently known for a scope. It does not trigger +discovery; call [`refreshEnvironments`](#refreshenvironments) for that. ```typescript -removeEnvironment( - environment: PythonEnvironment, - options?: RemoveEnvironmentOptions, -): Promise +getEnvironments(scope: GetEnvironmentsScope): Promise; ``` -Removes an environment through its owning manager. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `scope` | [`GetEnvironmentsScope`](#scope-types) | Yes | `Uri` for environments associated with a project, folder, or file; `'all'` for every known environment; `'global'` for base installations suitable for creating virtual environments. | + +**Returns** `Promise` - possibly empty; never `undefined`. ```typescript -await api.removeEnvironment(environment, { - runHeadless: true, -}); +const all = await api.getEnvironments('all'); +const bases = await api.getEnvironments('global'); +const forProject = await api.getEnvironments(projectUri); ``` -`RemoveEnvironmentOptions.runHeadless` requests removal without a confirmation -prompt. +#### `refreshEnvironments` -### `onDidChangeEnvironments` +Asks the relevant environment managers to re-discover environments. ```typescript -onDidChangeEnvironments: Event<{ - kind: EnvironmentChangeKind; - environment: PythonEnvironment; -}[]> +refreshEnvironments(scope: RefreshEnvironmentsScope): Promise; ``` -Fires with one or more `add` or `remove` changes to discovered environments. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `scope` | [`RefreshEnvironmentsScope`](#scope-types) | Yes (may be `undefined`) | `Uri` refreshes discovery for that project or folder; `undefined` refreshes global and workspace discovery. | -### `onDidChangeEnvironment` +**Returns** `Promise`, resolving when discovery completes. Results arrive +through [`onDidChangeEnvironments`](#ondidchangeenvironments); subscribe before +refreshing if you need the deltas. ```typescript -onDidChangeEnvironment: Event<{ - readonly uri: Uri | undefined; - readonly old: PythonEnvironment | undefined; - readonly new: PythonEnvironment | undefined; -}> +await api.refreshEnvironments(undefined); +const refreshed = await api.getEnvironments('all'); ``` -Fires when the selected environment changes. `uri` is `undefined` for a global -selection change. - -## Package methods +#### `resolveEnvironment` -Package operations use the package manager associated with a -`PythonEnvironment`. - -### `getPackages` +Turns an interpreter path or environment folder into a fully populated +`PythonEnvironment`, including `execInfo`. ```typescript -getPackages( - environment: PythonEnvironment, - options?: GetPackagesOptions, -): Promise +resolveEnvironment( + context: ResolveEnvironmentContext, +): Promise; ``` -Gets installed packages. It returns `undefined` when package information is -unavailable. Set `skipCache: true` to query the underlying package tool. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `context` | [`ResolveEnvironmentContext`](#scope-types) (`Uri`) | Yes | A `Uri` pointing at a Python executable or at an environment folder. | + +**Returns** `Promise` - `undefined` when no +manager recognizes the URI. ```typescript -const cachedPackages = await api.getPackages(environment); -const currentPackages = await api.getPackages(environment, { - skipCache: true, -}); +const env = await api.resolveEnvironment( + vscode.Uri.file('/usr/local/bin/python3.12'), +); ``` -`Package` extends `PackageInfo` with a `pkgId` containing `id`, `managerId`, and -`environmentId`. `PackageInfo` contains: +> [!IMPORTANT] +> `ResolveEnvironmentContext` is `Uri` only. Even though nearby source comments +> mention environments, the exported signature does not accept a +> `PythonEnvironment`. Pass `env.environmentPath` if you have an environment +> and want it re-resolved. -| Property | Type | Required | -| --- | --- | --- | -| `name` | `string` | Yes | -| `displayName` | `string` | Yes | -| `version` | `string` | No | -| `description` | `string` | No | -| `tooltip` | `string \| MarkdownString` | No | -| `iconPath` | `IconPath` | No | -| `uris` | `readonly Uri[]` | No | -| `isTransitive` | `boolean` | No | +#### `getEnvironment` -### `refreshPackages` +Reads the environment currently selected for a scope. ```typescript -refreshPackages(environment: PythonEnvironment): Promise +getEnvironment(scope: GetEnvironmentScope): Promise; ``` -Refreshes package information for an environment. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `scope` | [`GetEnvironmentScope`](#scope-types) | Yes (may be `undefined`) | `Uri` of a project, folder, or file to read the selection for; `undefined` reads the global selection. | + +**Returns** `Promise` - `undefined` when nothing +is selected for the scope. ```typescript -await api.refreshPackages(environment); +const active = await api.getEnvironment( + vscode.window.activeTextEditor?.document.uri, +); ``` -### `managePackages` +#### `setEnvironment` + +Selects - or clears - the environment for one or more scopes, and persists the +selection. ```typescript -managePackages( - environment: PythonEnvironment, - options: PackageManagementOptions, -): Promise +setEnvironment( + scope: SetEnvironmentScope, + environment?: PythonEnvironment, +): Promise; ``` -`PackageManagementOptions` requires `install`, `uninstall`, or both: - -```typescript -await api.managePackages(environment, { - install: ['requests', 'pytest'], - upgrade: true, -}); +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `scope` | [`SetEnvironmentScope`](#scope-types) | Yes (may be `undefined`) | `Uri` or `Uri[]` for the projects to update; `undefined` updates the global selection. | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | No | The environment to select. Omit it to clear the selection for the scope. | -await api.managePackages(environment, { - uninstall: ['requests'], -}); +**Returns** `Promise`. Fires +[`onDidChangeEnvironment`](#ondidchangeenvironment). -await api.managePackages(environment, { - install: ['requests'], - uninstall: ['urllib3'], - runHeadless: true, -}); +```typescript +await api.setEnvironment(projectUri, env); +await api.setEnvironment(projectUri); // clear ``` -| Option | Meaning | -| --- | --- | -| `install` | Package names or install arguments. | -| `uninstall` | Package names to uninstall. | -| `upgrade` | Upgrade packages that are already installed. | -| `showSkipOption` | Let an interactive flow offer to skip the operation. | -| `runHeadless` | Run without prompts and rely on the supplied package lists. | +#### `createEnvironment` -### `getPackageAvailableVersions` +Creates an environment using the environment manager associated with the scope. ```typescript -getPackageAvailableVersions( - environment: PythonEnvironment, - packageName: string, - options: { errorMode: 'throw' }, -): Promise - -getPackageAvailableVersions( - environment: PythonEnvironment, - packageName: string, - options?: { errorMode?: 'legacy' | 'throw' }, -): Promise +createEnvironment( + scope: CreateEnvironmentScope, + options?: CreateEnvironmentOptions, +): Promise; ``` -The default, legacy mode returns `undefined` when lookup is unsupported or -fails: +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `scope` | [`CreateEnvironmentScope`](#scope-types) | Yes | `Uri` or `Uri[]` for the projects the environment is created for; `'global'` creates one outside any project. | +| `options` | [`CreateEnvironmentOptions`](#createenvironmentoptions) | No | Controls prompting (`quickCreate`) and extra packages (`additionalPackages`). | + +**Returns** `Promise` - `undefined` when no +environment was created, for example because the user cancelled the flow. +Rejects when no environment manager is registered for the scope, when the +manager does not support creation, or when creation itself fails - so handle +errors as well as `undefined`. ```typescript -const versions = await api.getPackageAvailableVersions( - environment, - 'requests', -); +const created = await api.createEnvironment(projectUri, { + quickCreate: true, + additionalPackages: ['requests'], +}); +if (created) { + await api.setEnvironment(projectUri, created); +} ``` -New integrations should use throw mode when they need to distinguish an -unsupported capability from an operational failure: +> [!NOTE] +> Creation is not guaranteed to select the new environment. Call +> [`setEnvironment`](#setenvironment) if your feature depends on it being +> active. -```typescript -import { - isPackageVersionLookupNotSupportedError, -} from '@vscode/python-environments'; +#### `removeEnvironment` -try { - const versions = await api.getPackageAvailableVersions( - environment, - 'requests', - { errorMode: 'throw' }, - ); -} catch (error) { - if (isPackageVersionLookupNotSupportedError(error)) { - // Offer manual version entry or hide version suggestions. - } else { - throw error; - } -} +Removes an environment through its owning manager. + +```typescript +removeEnvironment( + environment: PythonEnvironment, + options?: RemoveEnvironmentOptions, +): Promise; ``` -With `{ errorMode: 'throw' }`, unsupported lookup rejects with -`PackageVersionLookupNotSupportedError`; operational failures propagate -unchanged. Use the exported type guard instead of relying only on `instanceof`, -because extensions may bundle separate copies of the API package. -The error exposes the stable code `PackageVersionLookupNotSupported`. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment to remove. Its `envId.managerId` must belong to a registered manager, so pass an environment the API returned. | +| `options` | [`RemoveEnvironmentOptions`](#removeenvironmentoptions) | No | Set `runHeadless: true` to skip the confirmation prompt. | -### `onDidChangePackages` +**Returns** `Promise`, resolving when removal completes. Rejects when the +manager fails to remove the environment. ```typescript -onDidChangePackages: Event<{ - environment: PythonEnvironment; - manager: PackageManager; - changes: { kind: PackageChangeKind; pkg: Package }[]; -}> +await api.removeEnvironment(env, { runHeadless: true }); ``` -Fires when packages are added or removed. `PackageChangeKind` contains `add` -and `remove`. +> [!WARNING] +> Removal is defined by the owning manager and is usually destructive - for a +> local virtual environment it deletes the environment from disk. Only pass +> `runHeadless: true` when the user has already agreed, or in automated tests. -## Project methods +#### `createPythonEnvironmentItem` -A `PythonProject` represents a folder or file that can have its own Python -environment. Workspace folders are projects by default. +Converts a provider's descriptive info into an identified `PythonEnvironment`. +Synchronous, and intended for environment manager implementations. ```typescript -interface PythonProject { - readonly name: string; - readonly uri: Uri; - readonly description?: string; - readonly tooltip?: string | MarkdownString; -} +createPythonEnvironmentItem( + info: PythonEnvironmentInfo, + manager: EnvironmentManager, +): PythonEnvironment; ``` -### `getPythonProjects` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `info` | [`PythonEnvironmentInfo`](#pythonenvironmentinfo) | Yes | Descriptive details of the environment, including `execInfo` and `sysPrefix`. | +| `manager` | [`EnvironmentManager`](#environmentmanager) | Yes | The manager that owns the environment; supplies `managerId` in the resulting `envId`. | + +**Returns** [`PythonEnvironment`](#pythonenvironment) with a valid `envId`. ```typescript -getPythonProjects(): readonly PythonProject[] +const env = api.createPythonEnvironmentItem(info, this); ``` -Synchronously returns all known projects. +#### `onDidChangeEnvironments` -### `getPythonProject` +Fires when environments are discovered or removed. ```typescript -getPythonProject(uri: Uri): PythonProject | undefined +onDidChangeEnvironments: Event; ``` -Synchronously returns the project associated with a URI. +| Payload | Type | Description | +| --- | --- | --- | +| `e` | [`DidChangeEnvironmentsEventArgs`](#didchangeenvironmentseventargs) | Array of `{ kind, environment }` entries describing each change. | -```typescript -const projects = api.getPythonProjects(); -const project = api.getPythonProject(document.uri); +**Returns** a `Disposable` from the subscription; add it to +`context.subscriptions`. -if (project !== undefined) { - const environment = await api.getEnvironment(project.uri); -} +```typescript +context.subscriptions.push( + api.onDidChangeEnvironments((changes) => { + for (const change of changes) { + console.log(change.kind, change.environment.displayName); + } + }), +); ``` -### `addPythonProject` +#### `onDidChangeEnvironment` + +Fires when the selected environment changes for a project, folder, file, or the +global scope. ```typescript -addPythonProject( - projects: PythonProject | PythonProject[], -): void +onDidChangeEnvironment: Event; ``` -```typescript -import { PythonProject } from '@vscode/python-environments'; +| Payload | Type | Description | +| --- | --- | --- | +| `e` | [`DidChangeEnvironmentEventArgs`](#didchangeenvironmenteventargs) | `{ uri, old, new }` describing the scope and the selection transition. | -const project: PythonProject = { - name: 'Backend', - uri: backendUri, - description: 'Backend service', -}; +**Returns** a `Disposable` from the subscription. -api.addPythonProject(project); +```typescript +context.subscriptions.push( + api.onDidChangeEnvironment((e) => { + console.log(e.uri?.fsPath ?? 'global', '->', e.new?.displayName); + }), +); ``` -### `removePythonProject` +## Packages -```typescript -removePythonProject(project: PythonProject): void -``` +Package methods read and change what is installed in an environment. Packages +are supplied by package managers (pip, conda, uv, and any manager contributed +by another extension) and are identified by `pkgId`. -`removePythonProject()` removes the project from tracking; it does not describe -a filesystem deletion operation. +### Package data types -### `onDidChangePythonProjects` +#### `PackageId` -```typescript -onDidChangePythonProjects: Event<{ - added: PythonProject[]; - removed: PythonProject[]; -}> +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `id` | `string` | Yes | Unique identifier of the package within its manager and environment. | +| `managerId` | `string` | Yes | Identifier of the package manager that reported the package. | +| `environmentId` | `string` | Yes | Identifier of the environment the package is installed in. | + +#### `PackageInfo` + +The descriptive payload of a package. Providers build this and pass it to +[`createPackageItem`](#createpackageitem). + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Distribution name, for example `requests`. | +| `displayName` | `string` | Yes | Name shown in the packages view. | +| `version` | `string` | No | Installed version, when the manager can report one. | +| `description` | `string` | No | Summary text shown next to the package. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the package. | +| `iconPath` | [`IconPath`](#iconpath) | No | Icon shown for the package. | +| `uris` | `readonly Uri[]` | No | Related locations, such as the installed distribution folder. | +| `isTransitive` | `boolean` | No | `true` when the package was pulled in as a dependency rather than requested directly. | + +```typescript +const info: PackageInfo = { + name: 'requests', + displayName: 'requests', + version: '2.31.0', + description: 'HTTP for Humans', +}; ``` -Fires after projects are added to or removed from the tracked collection. +#### `Package` -See [Making and Managing Python Projects](managing-python-projects.md) for -project-oriented user workflows. +`PackageInfo` plus its identity; this is what `getPackages` returns. -## Execution methods +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `pkgId` | [`PackageId`](#packageid) | Yes | Identity of the package. Use this - there is no `id` property. | +| *(inherited)* | [`PackageInfo`](#packageinfo) | - | All descriptive fields listed above. | -All execution methods require a `PythonEnvironment`. +#### `GetPackagesOptions` -### `PythonEnvironmentExecutionInfo` +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `skipCache` | `boolean` | No | `true` bypasses the cache and queries the underlying tool. Defaults to `false`. | -The environment describes execution with a required `run` command and optional -`activatedRun`, `activation`, `shellActivation`, `deactivation`, and -`shellDeactivation` commands. Each `PythonCommandRunConfiguration` contains an -absolute, spawnable `executable` and optional `args`. +#### `PackageManagementInteractionOptions` -### `createTerminal` +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `runHeadless` | `boolean` | No | `true` runs without prompts and uses only the packages given in the options. Steps that would normally prompt - such as asking which packages to install - are skipped. Defaults to `false`. | -```typescript -createTerminal( - environment: PythonEnvironment, - options: PythonTerminalCreateOptions, -): Promise +#### `PackageManagementOptions` + +An intersection of +[`PackageManagementInteractionOptions`](#packagemanagementinteractionoptions) +with a union that requires **at least one** of `install` or `uninstall`. A +literal with neither does not compile. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `install` | `string[]` | Conditional | Requirement specifiers to install. Required unless `uninstall` is provided. | +| `uninstall` | `string[]` | Conditional | Package names to uninstall. Required unless `install` is provided. | +| `upgrade` | `boolean` | No | `true` upgrades packages that are already installed. | +| `showSkipOption` | `boolean` | No | `true` offers the user a way to skip the operation. | +| `runHeadless` | `boolean` | No | Inherited interaction flag; `true` suppresses all prompts. | + +```typescript +// Valid: install only +const a: PackageManagementOptions = { install: ['requests'] }; +// Valid: uninstall only +const b: PackageManagementOptions = { uninstall: ['requests'] }; +// Valid: both, headless +const c: PackageManagementOptions = { + install: ['httpx'], + uninstall: ['requests'], + runHeadless: true, +}; +// Does not compile: neither install nor uninstall +// const d: PackageManagementOptions = { upgrade: true }; ``` -`PythonTerminalCreateOptions` extends VS Code's `TerminalOptions` and adds -`disableActivation?: boolean`. +#### `GetPackageAvailableVersionsOptions` -```typescript -const terminal = await api.createTerminal(environment, { - name: 'Python tools', - cwd: project.uri, - disableActivation: false, -}); +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `errorMode` | `'legacy' \| 'throw'` | No | `'legacy'` (the default) resolves to `undefined` for both unsupported lookups and operational failures. `'throw'` rejects with `PackageVersionLookupNotSupportedError` when lookup is unsupported and lets operational failures propagate unchanged. | -terminal.show(); -``` +> [!TIP] +> Prefer `{ errorMode: 'throw' }`. `'legacy'` is kept for backward +> compatibility and may be removed in a future major version, and it cannot +> distinguish "not supported" from "the network failed". -### `runInTerminal` +#### `Pep440Version` -```typescript -runInTerminal( - environment: PythonEnvironment, - options: PythonTerminalExecutionOptions, -): Promise -``` +Re-exported from `@renovatebot/pep440`. Represents a parsed PEP 440 version, +returned by `getPackageAvailableVersions` and by a package manager's +`getVersion`. Import it from `@vscode/python-environments` so your types match +the API exactly. -Runs Python in an available project terminal, creating one when necessary. -`PythonTerminalExecutionOptions` requires `cwd: string | Uri` and optionally -accepts `args: string[]` and `show: boolean`. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `public` | `string` | Yes | The normalized version string, for example `2.31.0`. This is the field to display or feed back into a specifier - `Pep440Version` is a plain data object, so `toString()` yields `[object Object]`. | +| `base_version` | `string` | Yes | The release segment only, with pre/post/dev/local parts stripped. | +| `is_prerelease` | `boolean` | Yes | `true` for alpha, beta, release-candidate, and dev versions. | +| `is_devrelease` | `boolean` | Yes | `true` when the version carries a `.devN` segment. | +| `is_postrelease` | `boolean` | Yes | `true` when the version carries a `.postN` segment. | +| `epoch` | `number` | Yes | PEP 440 epoch; `0` unless the project has reset its versioning scheme. | +| `release` | `number[]` | Yes | Release segment components, for example `[2, 31, 0]`. | +| `pre` | `(string \| number)[]` | Yes | Pre-release segment, for example `['rc', 1]`; empty when absent. | +| `post` | `(string \| number)[]` | Yes | Post-release segment; empty when absent. | +| `dev` | `(string \| number)[]` | Yes | Development-release segment; empty when absent. | +| `local` | `string \| null` | Yes | Local version label, or `null` when absent. | ```typescript -await api.runInTerminal(environment, { - cwd: project.uri, - args: ['script.py', '--verbose'], - show: true, -}); +// Show the newest non-prerelease version, if the manager returned any. +const stable = versions.find((v) => !v.is_prerelease); +console.log(stable?.public ?? 'no stable release found'); ``` -### `runInDedicatedTerminal` +#### `PackageChangeKind` + +| Member | Value | Description | +| --- | --- | --- | +| `PackageChangeKind.add` | `'add'` | A package was installed. | +| `PackageChangeKind.remove` | `'remove'` | A package was uninstalled. | + +#### `DidChangePackagesEventArgs` + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment whose packages changed. | +| `manager` | [`PackageManager`](#packagemanager) | Yes | The package manager that reported the change. | +| `changes` | `{ kind: PackageChangeKind; pkg: Package }[]` | Yes | One entry per changed package. | + +### Package methods + +#### `getPackages` + +Returns the packages installed in an environment. ```typescript -runInDedicatedTerminal( - terminalKey: Uri | string, +getPackages( environment: PythonEnvironment, - options: PythonTerminalExecutionOptions, -): Promise + options?: GetPackagesOptions, +): Promise; ``` -Runs Python in a terminal selected by a stable URI or string key. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment to inspect. | +| `options` | [`GetPackagesOptions`](#getpackagesoptions) | No | Set `skipCache: true` to bypass the cache. | + +**Returns** `Promise`. `undefined` means the manager +could not produce a list - for example no package manager is associated with +the environment - which is different from an empty array meaning "nothing +installed". ```typescript -await api.runInDedicatedTerminal( - document.uri, - environment, - { - cwd: project.uri, - args: [document.uri.fsPath], - show: true, - }, -); +const packages = await api.getPackages(env); +if (packages === undefined) { + // Package listing unavailable for this environment. +} else { + const direct = packages.filter((p) => !p.isTransitive); +} ``` -`runInDedicatedTerminal()` accepts a `Uri` or string key. Reuse a stable key for -work that should use the same dedicated terminal. +#### `refreshPackages` -### `runAsTask` +Forces the package manager to re-read the installed package list. ```typescript -runAsTask( - environment: PythonEnvironment, - options: PythonTaskExecutionOptions, -): Promise +refreshPackages(environment: PythonEnvironment): Promise; ``` -`PythonTaskExecutionOptions` requires `name` and `args`; it optionally accepts a -`project`, `cwd`, and string-valued `env`. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment whose package list should be refreshed. | + +**Returns** `Promise`. Changes surface through +[`onDidChangePackages`](#ondidchangepackages). ```typescript -const execution = await api.runAsTask(environment, { - name: 'Run tests', - args: ['-m', 'pytest', '-q'], - project, - cwd: project.uri.fsPath, - env: { - PYTHONUNBUFFERED: '1', - }, -}); +// Packages were installed outside the extension - re-read the list. +await api.refreshPackages(env); +const packages = await api.getPackages(env); ``` -### `runInBackground` +#### `managePackages` + +Installs, upgrades, and uninstalls packages in one call. ```typescript -runInBackground( +managePackages( environment: PythonEnvironment, - options: PythonBackgroundRunOptions, -): Promise + options: PackageManagementOptions, +): Promise; ``` -Starts a new process. `PythonBackgroundRunOptions` requires `args`; `cwd` and -`env` are optional. - -```typescript -const process = await api.runInBackground(environment, { - args: ['-m', 'http.server', '8000'], - cwd: project.uri.fsPath, - env: { - PYTHONUNBUFFERED: '1', - }, -}); - -process.stdout.on('data', (data) => { - output.append(data.toString()); -}); +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment to modify. | +| `options` | [`PackageManagementOptions`](#packagemanagementoptions) | Yes | Must specify `install`, `uninstall`, or both. Also carries `upgrade`, `showSkipOption`, and `runHeadless`. | -process.stderr.on('data', (data) => { - output.append(data.toString()); -}); +**Returns** `Promise`, resolving when the operation finishes. Rejects if +the underlying tool fails. -process.onExit((code, signal) => { - output.appendLine(`Python exited: code=${code}, signal=${signal}`); +```typescript +await api.managePackages(env, { + install: ['requests', 'rich'], + uninstall: ['obsolete-package'], + upgrade: true, }); ``` -`PythonProcess` exposes `pid`, `stdin`, `stdout`, `stderr`, `kill()`, and -`onExit()`. Unlike a VS Code `Event`, `onExit()` does not return a `Disposable`. +> [!NOTE] +> Plain package names are the portable choice. Version pinning syntax belongs to +> the package manager - `requests==2.31.0` is pip's format, and conda and others +> differ. A manager that implements `formatInstallSpec` produces the right +> string for its own tool; do not assume `name==version`. -## Environment variable methods +#### `getPackageAvailableVersions` -### `getEnvironmentVariables` +Looks up the versions available for a package, newest first. ```typescript -getEnvironmentVariables( - uri: Uri | undefined, - overrides?: ({ [key: string]: string | undefined } | Uri)[], - baseEnvVar?: { [key: string]: string | undefined }, -): Promise<{ [key: string]: string | undefined }> +// Overload 1 - recommended +getPackageAvailableVersions( + environment: PythonEnvironment, + packageName: string, + options: GetPackageAvailableVersionsOptions & { errorMode: 'throw' }, +): Promise; + +// Overload 2 - legacy default +getPackageAvailableVersions( + environment: PythonEnvironment, + packageName: string, + options?: GetPackageAvailableVersionsOptions, +): Promise; ``` -`getEnvironmentVariables()` combines process, configured, project, and caller -variables: +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment context for the lookup; determines which package manager answers. | +| `packageName` | `string` | Yes | Name of the package to look up. | +| `options` | [`GetPackageAvailableVersionsOptions`](#getpackageavailableversionsoptions) | No (overload 2) / Yes (overload 1) | Pass `{ errorMode: 'throw' }` to select the first overload and get error-mode reporting. | + +**Returns** + +| Call form | Return type | Failure behavior | +| --- | --- | --- | +| `{ errorMode: 'throw' }` | `Promise` | Rejects with `PackageVersionLookupNotSupportedError` when unsupported; operational errors propagate unchanged. | +| Omitted or `{ errorMode: 'legacy' }` | `Promise` | Resolves to `undefined` for both unsupported lookups and operational failures. | ```typescript -const variables = await api.getEnvironmentVariables( - project.uri, - [ - commonEnvironmentFileUri, - { MY_EXTENSION_MODE: 'analysis' }, - ], - { - PATH: process.env.PATH, - PYTHONUTF8: '1', - }, -); -``` +import { isPackageVersionLookupNotSupportedError } from '@vscode/python-environments'; -Values are applied from lowest to highest precedence: +try { + const versions = await api.getPackageAvailableVersions(env, 'requests', { + errorMode: 'throw', + }); + // The array can be empty, and `Pep440Version` is a data object - read `public`. + const newest = versions[0]?.public; + if (newest) { + void vscode.window.showInformationMessage(`Newest requests: ${newest}`); + } +} catch (error) { + if (isPackageVersionLookupNotSupportedError(error)) { + // Fall back to manual version entry. + } else { + throw error; // Real failure: surface it. + } +} +``` -1. `baseEnvVar`, or `process.env` when it is omitted. -2. The file configured by the `python.envFile` setting. -3. The `.env` file at the Python project root. -4. Each `overrides` entry in array order. +> [!IMPORTANT] +> Do not paste a returned version straight into an `install` entry. The +> specifier syntax belongs to the package manager - `name==version` is pip's +> format, not a universal one. A manager that implements `formatInstallSpec` +> builds the correct string for its own tool. -An override can be a URI for an environment file or an object whose values are -`string | undefined`. Pass `undefined` as the first argument for global scope. +#### `createPackageItem` -### `onDidChangeEnvironmentVariables` +Converts a provider's descriptive info into an identified `Package`. +Synchronous, and intended for package manager implementations. ```typescript -onDidChangeEnvironmentVariables: - Event +createPackageItem( + info: PackageInfo, + environment: PythonEnvironment, + manager: PackageManager, +): Package; ``` -Subscribe to changes when cached results depend on these variables: +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `info` | [`PackageInfo`](#packageinfo) | Yes | Descriptive details of the package. | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | The environment the package is installed in; supplies `environmentId`. | +| `manager` | [`PackageManager`](#packagemanager) | Yes | The reporting package manager; supplies `managerId`. | + +**Returns** [`Package`](#package) with a valid `pkgId`. ```typescript -context.subscriptions.push( - api.onDidChangeEnvironmentVariables((event) => { - const changedFile = event.uri; - const changeType = event.changeType; - }), +// Inside a PackageManager implementation: +const pkg = api.createPackageItem( + { name: 'requests', displayName: 'requests', version: '2.31.0' }, + environment, + this, ); ``` -The URI is absent when a non-file source changes. `changeType` is VS Code's -`FileChangeType`. - -## Provider methods +#### `onDidChangePackages` -### `registerEnvironmentManager` +Fires when packages are installed or removed. ```typescript -registerEnvironmentManager( - manager: EnvironmentManager, - options?: { extensionId?: string }, -): Disposable +onDidChangePackages: Event; ``` -Registers an environment manager and returns a disposable that unregisters it. -When `extensionId` is omitted, or cannot be found, the API attempts to detect -the calling extension. +| Payload | Type | Description | +| --- | --- | --- | +| `e` | [`DidChangePackagesEventArgs`](#didchangepackageseventargs) | The environment, the reporting manager, and the list of `{ kind, pkg }` changes. | + +**Returns** a `Disposable` from the subscription. ```typescript context.subscriptions.push( - api.registerEnvironmentManager(manager, { - extensionId: context.extension.id, + api.onDidChangePackages((e) => { + console.log(e.environment.displayName, e.changes.length); }), ); ``` -#### `EnvironmentManager` +### Package errors + +#### `PackageVersionLookupNotSupportedError` + +Thrown when a package manager cannot list available versions at all. It +separates an *unsupported capability* from an *operational failure* such as a +failed command, a network error, or unparseable output. -An `EnvironmentManager` discovers environments, controls environment -selection, and can optionally create and remove environments. +| Member | Type | Required | Description | +| --- | --- | --- | --- | +| `code` | `'PackageVersionLookupNotSupported'` | Yes | Stable discriminator that survives bundle boundaries. Declared `readonly`. | +| `name` | `string` | Yes | Inherited from `Error`. Its runtime value is set to `'PackageVersionLookupNotSupportedError'`, but its static type stays `string`, so do not assign it to a string-literal type. | +| `message` | `string` | Yes | Inherited from `Error`. Defaults to an explanation that version lookup is unsupported. | -| Member | Required | Purpose | -| --- | --- | --- | -| `name` | Yes | Provider-local ID containing only letters, numbers, `-`, and `_`. | -| `preferredPackageManagerId` | Yes | Fully qualified ID of the preferred package manager. | -| `refresh(scope)` | Yes | Re-discover environments for the scope. | -| `getEnvironments(scope)` | Yes | Return environments known in the scope. | -| `set(scope, environment?)` | Yes | Apply or clear the selected environment. | -| `get(scope)` | Yes | Return the selected environment. | -| `resolve(context)` | Yes | Resolve a URI to an environment or return `undefined`. | -| `create(scope, options?)` | No | Create an environment. | -| `remove(environment, options?)` | No | Remove an environment. | -| `quickCreateConfig()` | No | Describe the manager's quick-create option. | -| `clearCache()` | No | Clear provider-owned environment caches. | -| `onDidChangeEnvironments` | No | Report discovered environment changes. | -| `onDidChangeEnvironment` | No | Report selection changes. | - -Optional metadata includes `displayName`, `description`, `tooltip`, `iconPath`, -and a `LogOutputChannel`. - -Omit unsupported optional methods rather than implementing methods that always -throw. `quickCreateConfig()` enables quick-create UI only when the manager also -implements `create()`. - -### `createPythonEnvironmentItem` +Providers throw it with `new PackageVersionLookupNotSupportedError(message?)`; +the single `message` parameter is optional. ```typescript -createPythonEnvironmentItem( - info: PythonEnvironmentInfo, - manager: EnvironmentManager, -): PythonEnvironment +import { PackageVersionLookupNotSupportedError } from '@vscode/python-environments'; + +// Inside a PackageManager implementation: +async getPackageAvailableVersions(): Promise { + throw new PackageVersionLookupNotSupportedError(); +} ``` -Use `createPythonEnvironmentItem()` rather than constructing `envId`: +#### `isPackageVersionLookupNotSupportedError` ```typescript -const environment = api.createPythonEnvironmentItem( - { - name: discovered.name, - displayName: discovered.displayName, - displayPath: discovered.executable.fsPath, - version: discovered.version, - environmentPath: discovered.executable, - sysPrefix: discovered.sysPrefix, - execInfo: { - run: { - executable: discovered.executable.fsPath, - }, - }, - }, - manager, -); +isPackageVersionLookupNotSupportedError( + error: unknown, +): error is PackageVersionLookupNotSupportedError; ``` -`PythonEnvironmentInfo` requires complete execution information and -`sysPrefix`. Register the manager before publishing items created for it. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `error` | `unknown` | Yes | The caught value to test. | -The manager may be called by startup, UI, terminal, execution, and other -extension workflows. Implementations should: +**Returns** `boolean` (a type guard). Checks the stable `code` discriminator, so +it still returns `true` when the error crossed an extension bundle boundary. -- make `get()` and `getEnvironments()` efficient and safe to call repeatedly; -- update internal state before firing change events; -- return `undefined` from `resolve()` when the URI is not recognized; -- return complete execution details for resolved environments; -- treat `refresh()` as an explicit request to rediscover state; -- clear provider-owned state when `clearCache()` is called; and -- dispose their own event emitters, watchers, processes, and output channels. +> [!IMPORTANT] +> Always use this guard instead of `instanceof`. Each extension bundle can load +> its own copy of the error class, so `instanceof` can return `false` for an +> error that is semantically the right one. -### `registerPackageManager` +## Projects -```typescript -registerPackageManager( - manager: PackageManager, - options?: { extensionId?: string }, -): Disposable -``` +A project is anything that can own a Python environment: a workspace folder, a +subfolder, or even a single PEP 723 script. Every +`vscode.workspace.workspaceFolders` entry is a project by default. + +### Project data types + +#### `PythonProject` -Registers a package manager and returns a disposable that unregisters it. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Display name of the project. | +| `uri` | `Uri` | Yes | Root folder, or the file for file-based projects. Used to match a project to a file. | +| `description` | `string` | No | Secondary text shown next to the project. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the project. | ```typescript -context.subscriptions.push( - api.registerPackageManager(packageManager, { - extensionId: context.extension.id, - }), -); +const project: PythonProject = { + name: 'service-api', + uri: vscode.Uri.file('/home/me/code/app/service-api'), + description: 'FastAPI service', +}; ``` -Set an environment manager's `preferredPackageManagerId` to the fully qualified -ID of the package manager intended to handle its environments. +#### `PythonProjectCreatorOptions` -#### `PackageManager` +Passed to a [`PythonProjectCreator`](#pythonprojectcreator). -A `PackageManager` reports installed packages and performs package operations -for environments. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Name for the project being created. | +| `rootUri` | `Uri` | Yes | Folder to use as the project root. | +| `quickCreate` | `boolean` | No | `true` requires creation to complete without any user input. | -| Member | Required | Purpose | -| --- | --- | --- | -| `name` | Yes | Provider-local ID containing supported manager-name characters. | -| `manage(environment, options)` | Yes | Install or uninstall packages. | -| `refresh(environment)` | Yes | Refresh package state. | -| `getPackages(environment, options?)` | Yes | Return installed packages or `undefined`. | -| `getPackageWatchTargets(environment)` | No | Add manager-specific filesystem watch patterns. | -| `getDirectPackageNames(environment)` | No | Return a best-effort set of direct package names. | -| `clearCache()` | No | Clear provider-owned package caches. | -| `getVersion(environment)` | No | Return the package tool's PEP 440 version. | -| `getPackageAvailableVersions(environment, name)` | No | Return available versions, newest first. | -| `formatInstallSpec(name, version)` | No | Format a versioned install requirement. | -| `onDidChangePackages` | No | Report package additions and removals. | +#### `DidChangePythonProjectsEventArgs` + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `added` | `PythonProject[]` | Yes | Projects added in this change. May be empty. | +| `removed` | `PythonProject[]` | Yes | Projects removed in this change. May be empty. | -Optional metadata includes `displayName`, `description`, `tooltip`, `iconPath`, -and a `LogOutputChannel`. +### Project methods -When `formatInstallSpec()` is absent, callers should use `name==version`. -`getDirectPackageNames()` is best effort because many package tools cannot -distinguish explicit installation intent from packages with no installed -dependents. +#### `getPythonProjects` -### `createPackageItem` +Returns every project the extension currently tracks. Synchronous. ```typescript -createPackageItem( - info: PackageInfo, - environment: PythonEnvironment, - manager: PackageManager, -): Package +getPythonProjects(): readonly PythonProject[]; ``` +Takes no parameters. + +**Returns** `readonly PythonProject[]` - a snapshot; re-read it after +[`onDidChangePythonProjects`](#ondidchangepythonprojects) rather than caching. + ```typescript -const packageItem = api.createPackageItem( - { - name: discovered.name, - displayName: discovered.displayName, - version: discovered.version, - isTransitive: discovered.isTransitive, - }, - environment, - packageManager, -); +for (const project of api.getPythonProjects()) { + console.log(project.name, project.uri.fsPath); +} ``` -Use this helper instead of constructing `pkgId`. Register the package manager -before creating its items. +#### `getPythonProject` -#### Implementing version lookup +Finds the project that owns a URI. Synchronous. -A version lookup implementation should: - -- return `Pep440Version[]` in newest-first order on success; -- throw `PackageVersionLookupNotSupportedError` when the capability is - unsupported; and -- propagate command, network, and parsing failures unchanged. +```typescript +getPythonProject(uri: Uri): PythonProject | undefined; +``` -Returning `undefined` is allowed by the provider signature, but callers treat -it as an unsupported capability. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `uri` | `Uri` | Yes | A file or folder URI. The extension resolves the owning project, so a file inside a project resolves to that project. | -### `registerPythonProjectCreator` +**Returns** `PythonProject | undefined` - `undefined` when the URI is not inside +any known project. ```typescript -registerPythonProjectCreator( - creator: PythonProjectCreator, -): Disposable +const doc = vscode.window.activeTextEditor?.document; +const project = doc ? api.getPythonProject(doc.uri) : undefined; ``` -Registers a project creation workflow and returns a disposable that unregisters -it. +#### `addPythonProject` -A `PythonProjectCreator` contributes a project creation workflow. +Adds one or more projects to the tracked collection. Synchronous. -| Member | Required | Purpose | -| --- | --- | --- | -| `name` | Yes | Identify the creator. | -| `create(options?)` | Yes | Create projects or standalone files. | -| `displayName` | No | Provide a user-facing name. | -| `description` | No | Describe the creator. | -| `tooltip` | No | Provide additional UI detail. | -| `supportsQuickCreate` | No | Declare support for creation without user input. | +```typescript +addPythonProject(projects: PythonProject | PythonProject[]): void; +``` + +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `projects` | `PythonProject \| PythonProject[]` | Yes | The project or projects to track. Each needs at least `name` and `uri`. | + +**Returns** `void`. Fires +[`onDidChangePythonProjects`](#ondidchangepythonprojects) with the additions. -`create()` returns: +```typescript +api.addPythonProject({ + name: 'tools', + uri: vscode.Uri.joinPath(workspaceFolder.uri, 'tools'), +}); +``` -- `PythonProject` or `PythonProject[]` for created projects; -- `Uri` or `Uri[]` for created files that are not projects; or -- `undefined` when creation produces no result. +#### `removePythonProject` -When supplied, `PythonProjectCreatorOptions` contains a required project -`name`, a required `rootUri`, and an optional `quickCreate` flag. +Stops tracking a project. Synchronous. ```typescript -import * as vscode from 'vscode'; -import { - PythonProject, - PythonProjectCreator, - PythonProjectCreatorOptions, -} from '@vscode/python-environments'; +removePythonProject(project: PythonProject): void; +``` -class ExampleProjectCreator implements PythonProjectCreator { - public readonly name = 'example'; - public readonly displayName = 'Example project'; - public readonly supportsQuickCreate = true; +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `project` | [`PythonProject`](#pythonproject) | Yes | The project to remove. Obtain it from `getPythonProjects` or `getPythonProject` so it matches a tracked entry. | - public async create( - options?: PythonProjectCreatorOptions, - ): Promise { - if (options === undefined) { - return undefined; - } +**Returns** `void`. Fires +[`onDidChangePythonProjects`](#ondidchangepythonprojects) with the removal. - const uri = vscode.Uri.joinPath(options.rootUri, options.name); - await vscode.workspace.fs.createDirectory(uri); +Removing a project stops environment tracking for it; it does not delete +anything on disk. - return { - name: options.name, - uri, - }; - } +```typescript +const project = api.getPythonProject(projectUri); +if (project) { + api.removePythonProject(project); } ``` -Register the creator and retain its disposable: +#### `onDidChangePythonProjects` + +Fires when projects are added or removed. ```typescript -context.subscriptions.push( - api.registerPythonProjectCreator(projectCreator), -); +onDidChangePythonProjects: Event; ``` -## Errors and lifecycle - -### Events +| Payload | Type | Description | +| --- | --- | --- | +| `e` | [`DidChangePythonProjectsEventArgs`](#didchangepythonprojectseventargs) | `{ added, removed }` arrays for this change. | -API events follow the VS Code `Event` pattern. Store their disposables: +**Returns** a `Disposable` from the subscription. ```typescript context.subscriptions.push( - api.onDidChangeEnvironments((changes) => { - for (const change of changes) { - output.appendLine( - `${change.kind}: ${change.environment.displayName}`, - ); - } - }), - api.onDidChangeEnvironment(({ uri, old, new: current }) => { - // React to a selected environment change. - }), - api.onDidChangePackages(({ environment, manager, changes }) => { - // React to installed package changes. - }), - api.onDidChangePythonProjects(({ added, removed }) => { - // React to project collection changes. + api.onDidChangePythonProjects((e) => { + console.log('added', e.added.length, 'removed', e.removed.length); }), ); ``` -Change kinds are string enums: +## Execution + +Execution methods take an environment and use its +[`PythonEnvironmentExecutionInfo`](#pythonenvironmentexecutioninfo) - including +its activation commands, where the method applies them - so you do not have to +assemble activation yourself. Choose a method by where the output should go: a +terminal the user watches, a VS Code task, or a background process you read +programmatically. + +### Execution data types -- `EnvironmentChangeKind.add` and `EnvironmentChangeKind.remove`; -- `PackageChangeKind.add` and `PackageChangeKind.remove`. +#### `PythonCommandRunConfiguration` -Registration methods also return `Disposable` objects. Disposing a -registration unregisters that provider. Providers remain responsible for -resources they own. +A single command to execute. -### Errors and missing values +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `executable` | `string` | Yes | Absolute path to a spawnable binary, such as `python.exe` or `python3`. | +| `args` | `string[]` | No | Arguments passed on every execution of this command, for interpreter-specific flags. | -Handle rejected promises separately from `undefined` results: +#### `PythonEnvironmentExecutionInfo` -- `PythonEnvironments.api()` rejects when the extension or API is unavailable. -- `resolveEnvironment()` returns `undefined` when a URI cannot be resolved. -- `createEnvironment()` can return `undefined` when no environment is created. -- `getEnvironment()` returns `undefined` when no environment is selected. -- `getPackages()` can return `undefined` when packages are unavailable. -- legacy package version lookup returns `undefined` for unsupported lookup and - operational failure; -- throw-mode package version lookup distinguishes unsupported capability from - other failures. +Tells the extension how to run and activate an environment. Providers must +populate at least `run`. -Provider implementations should propagate operational failures rather than -turning them into successful-looking empty results unless the public contract -explicitly defines such a result. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `run` | [`PythonCommandRunConfiguration`](#pythoncommandrunconfiguration) | Yes | Base command used to run Python. | +| `activatedRun` | `PythonCommandRunConfiguration` | No | Command used to run Python *after* the environment has been activated. When set, it overrides `run`. | +| `activation` | `PythonCommandRunConfiguration[]` | No | Shell-agnostic commands that activate the environment. | +| `shellActivation` | `Map` | No | Shell-specific activation keyed by shell type, with `'unknown'` as the fallback key. Overrides `activation`. | +| `deactivation` | `PythonCommandRunConfiguration[]` | No | Shell-agnostic commands that deactivate the environment. | +| `shellDeactivation` | `Map` | No | Shell-specific deactivation keyed by shell type, with `'unknown'` as the fallback key. Overrides `deactivation`. | + +Resolution order when running in a terminal: + +1. `activatedRun`, if present. +2. Otherwise `shellActivation` for the detected shell; if the shell is unknown, + the `'unknown'` entry, then `activation`. +3. Otherwise `activation`. +4. Otherwise `run`. + +```typescript +const execInfo: PythonEnvironmentExecutionInfo = { + run: { executable: '/home/me/app/.venv/bin/python' }, + activation: [ + { executable: 'source', args: ['/home/me/app/.venv/bin/activate'] }, + ], + shellActivation: new Map([ + ['pwsh', [{ executable: '/home/me/app/.venv/bin/Activate.ps1' }]], + ]), +}; +``` -## Object reference +#### `PythonTerminalCreateOptions` -This section collects the objects, options, event payloads, and type aliases -referenced by the methods above. Provider interfaces are documented with their -registration methods in [Provider methods](#provider-methods). +Extends [`vscode.TerminalOptions`](https://code.visualstudio.com/api/references/vscode-api#TerminalOptions), +so every standard terminal option - `name`, `cwd`, `env`, `hideFromUser`, and +the rest - is available alongside the field below. -### Environment objects +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `disableActivation` | `boolean` | No | `true` creates the terminal without running activation commands. | +| *(inherited)* | [`vscode.TerminalOptions`](https://code.visualstudio.com/api/references/vscode-api#TerminalOptions) | - | See the VS Code API reference for the full list and their defaults. | -#### `PythonEnvironment` +#### `PythonTerminalExecutionOptions` -Returned by environment discovery, resolution, selection, and creation methods. -It combines [`PythonEnvironmentInfo`](#pythonenvironmentinfo) with an `envId`. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `cwd` | `string \| Uri` | Yes | Working directory. Used only when the terminal is created. | +| `args` | `string[]` | No | Arguments passed to the Python executable. | +| `show` | `boolean` | No | `true` reveals the terminal. | ```typescript -interface PythonEnvironment extends PythonEnvironmentInfo { - readonly envId: PythonEnvironmentId; -} +// python myscript.py --arg1 +const script: PythonTerminalExecutionOptions = { + cwd: projectUri, + args: ['myscript.py', '--arg1'], + show: true, +}; + +// python -m my_module --arg1 +const module: PythonTerminalExecutionOptions = { + cwd: projectUri, + args: ['-m', 'my_module', '--arg1'], +}; ``` -#### `PythonEnvironmentId` +#### `PythonTaskExecutionOptions` -Uniquely identifies an environment and its owning manager. +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Name of the task, shown in the task UI. | +| `args` | `string[]` | Yes | Arguments passed to the Python executable. | +| `project` | [`PythonProject`](#pythonproject) | No | Project the task belongs to. | +| `cwd` | `string` | No | Working directory. Defaults to the project directory of the script being run. | +| `env` | `{ [key: string]: string }` | No | Additional environment variables for the task. | -```typescript -interface PythonEnvironmentId { - id: string; - managerId: string; -} -``` +#### `PythonBackgroundRunOptions` -Use both properties for identity. See -[`PythonEnvironment` and identity](#pythonenvironment-and-identity). +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `args` | `string[]` | Yes | Arguments passed to the Python executable. | +| `cwd` | `string` | No | Working directory. Defaults to the project directory of the script being run. | +| `env` | `{ [key: string]: string \| undefined }` | No | Additional environment variables. An `undefined` value unsets a variable. | -#### `PythonEnvironmentInfo` +#### `PythonProcess` -Describes an environment before the API assigns its `envId`. It is passed to -[`createPythonEnvironmentItem()`](#createpythonenvironmentitem) and forms the -base of every returned `PythonEnvironment`. +Returned by [`runInBackground`](#runinbackground). -| Property | Type | Required | Description | +| Member | Type | Required | Description | | --- | --- | --- | --- | -| `name` | `string` | Yes | Environment name. | -| `displayName` | `string` | Yes | Primary user-facing name. | -| `displayPath` | `string` | Yes | User-facing path. | -| `version` | `string` | Yes | Python version. | -| `environmentPath` | `Uri` | Yes | Python executable or environment directory. | -| `execInfo` | `PythonEnvironmentExecutionInfo` | Yes | Commands for running and activating Python. | -| `sysPrefix` | `string` | Yes | Value of Python's `sys.prefix`. | -| `shortDisplayName` | `string` | No | Compact user-facing name. | -| `description` | `string` | No | Additional environment description. | -| `tooltip` | `string \| MarkdownString` | No | Hover text. | -| `iconPath` | `IconPath` | No | Environment icon. | -| `group` | `string \| EnvironmentGroupInfo` | No | Environment UI group. | -| `error` | `string` | No | Diagnostic for a broken or invalid environment. | +| `pid` | `number` | No | Process ID, when available. | +| `stdin` | `NodeJS.WritableStream` | Yes | Standard input stream. | +| `stdout` | `NodeJS.ReadableStream` | Yes | Standard output stream. | +| `stderr` | `NodeJS.ReadableStream` | Yes | Standard error stream. | +| `kill()` | `() => void` | Yes | Terminates the process. | +| `onExit(listener)` | `(listener: (code: number \| null, signal: NodeJS.Signals \| null) => void) => void` | Yes | Registers an exit listener receiving the exit code and signal. | -#### `PythonCommandRunConfiguration` +### Execution methods -Describes one executable invocation. +#### `createTerminal` + +Creates a terminal with the environment activated, without running anything. ```typescript -interface PythonCommandRunConfiguration { - executable: string; - args?: string[]; -} +createTerminal( + environment: PythonEnvironment, + options: PythonTerminalCreateOptions, +): Promise; ``` -`executable` must be an absolute path to an executable that can be spawned. -`args` are included on every invocation of that command. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment to activate in the new terminal. | +| `options` | [`PythonTerminalCreateOptions`](#pythonterminalcreateoptions) | Yes | Standard terminal options plus `disableActivation`. | -#### `PythonEnvironmentExecutionInfo` +**Returns** `Promise`. -Describes how to execute, activate, and deactivate an environment. +```typescript +const terminal = await api.createTerminal(env, { + name: 'My Extension', + cwd: projectUri, +}); +terminal.show(); +``` -| Property | Type | Required | Description | -| --- | --- | --- | --- | -| `run` | `PythonCommandRunConfiguration` | Yes | Default Python command. | -| `activatedRun` | `PythonCommandRunConfiguration` | No | Python command to use after activation. | -| `activation` | `PythonCommandRunConfiguration[]` | No | Generic activation commands. | -| `shellActivation` | `Map` | No | Activation commands by shell name. | -| `deactivation` | `PythonCommandRunConfiguration[]` | No | Generic deactivation commands. | -| `shellDeactivation` | `Map` | No | Deactivation commands by shell name. | +Environments that cannot be activated simply produce a normal terminal. -The `unknown` map key can provide a fallback when the shell type is not known. +#### `runInTerminal` -#### Environment scope aliases +Runs Python in a shared terminal, creating one if needed. -| Type | Definition | Referenced by | -| --- | --- | --- | -| `GetEnvironmentsScope` | `Uri \| 'all' \| 'global'` | `getEnvironments()` and manager discovery | -| `RefreshEnvironmentsScope` | `Uri \| undefined` | `refreshEnvironments()` and manager refresh | -| `ResolveEnvironmentContext` | `Uri` | `resolveEnvironment()` and manager resolution | -| `GetEnvironmentScope` | `Uri \| undefined` | `getEnvironment()` and manager selection lookup | -| `SetEnvironmentScope` | `Uri \| Uri[] \| undefined` | `setEnvironment()` and manager selection updates | -| `CreateEnvironmentScope` | `Uri \| Uri[] \| 'global'` | `createEnvironment()` and manager creation | +```typescript +runInTerminal( + environment: PythonEnvironment, + options: PythonTerminalExecutionOptions, +): Promise; +``` -#### `CreateEnvironmentOptions` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment used to run the command. | +| `options` | [`PythonTerminalExecutionOptions`](#pythonterminalexecutionoptions) | Yes | `cwd` plus the `args` to pass to Python and whether to `show` the terminal. | -Passed to [`createEnvironment()`](#createenvironment) and -`EnvironmentManager.create()`. +**Returns** `Promise` - the terminal the command was sent to. ```typescript -interface CreateEnvironmentOptions { - quickCreate?: boolean; - additionalPackages?: string[]; -} +await api.runInTerminal(env, { + cwd: projectUri, + args: ['-m', 'pytest', '-q'], + show: true, +}); ``` -`quickCreate: true` requests creation without input. `false` permits prompts -and records that quick create was skipped. When omitted, prompts are permitted -and the manager may offer quick create. +Terminal reuse has limits imposed by VS Code: reloading the window or closing +the terminal creates a new one, and multi-root or multi-project scenarios get +one terminal per project. -#### `RemoveEnvironmentOptions` +#### `runInDedicatedTerminal` -Passed to [`removeEnvironment()`](#removeenvironment) and -`EnvironmentManager.remove()`. +Like `runInTerminal`, but keeps a terminal per key so repeated runs reuse the +same one. ```typescript -interface RemoveEnvironmentOptions { - runHeadless?: boolean; -} +runInDedicatedTerminal( + terminalKey: Uri | string, + environment: PythonEnvironment, + options: PythonTerminalExecutionOptions, +): Promise; ``` -When `runHeadless` is true, removal should not prompt for confirmation. - -#### `QuickCreateConfig` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `terminalKey` | `Uri \| string` | Yes | Stable key identifying the dedicated terminal. Use the script's `Uri` for per-script terminals, or a string for a logical channel. | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment used to run the command. | +| `options` | [`PythonTerminalExecutionOptions`](#pythonterminalexecutionoptions) | Yes | `cwd`, `args`, and `show`. | -Returned by an environment manager's optional `quickCreateConfig()` method. +**Returns** `Promise` - the dedicated terminal for the key. ```typescript -interface QuickCreateConfig { - readonly description: string; - readonly detail?: string; -} +await api.runInDedicatedTerminal(scriptUri, env, { + cwd: projectUri, + args: [scriptUri.fsPath], + show: true, +}); ``` -#### `DidChangeEnvironmentsEventArgs` and `EnvironmentChangeKind` +#### `runAsTask` -`DidChangeEnvironmentsEventArgs` is an array of discovered-environment changes: +Runs Python as a VS Code task, so output appears in the task terminal and +problem matchers apply. ```typescript -type DidChangeEnvironmentsEventArgs = { - kind: EnvironmentChangeKind; - environment: PythonEnvironment; -}[]; - -enum EnvironmentChangeKind { - add = 'add', - remove = 'remove', -} +runAsTask( + environment: PythonEnvironment, + options: PythonTaskExecutionOptions, +): Promise; ``` -`DidChangeEnvironmentEventArgs` describes a selection change: +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment used to run the task. | +| `options` | [`PythonTaskExecutionOptions`](#pythontaskexecutionoptions) | Yes | Task `name` and `args`, plus optional `project`, `cwd`, and `env`. | + +**Returns** `Promise` - use it to observe or terminate the +task. ```typescript -type DidChangeEnvironmentEventArgs = { - readonly uri: Uri | undefined; - readonly old: PythonEnvironment | undefined; - readonly new: PythonEnvironment | undefined; -}; +const execution = await api.runAsTask(env, { + name: 'Run tests', + args: ['-m', 'pytest'], + project, +}); ``` -### Package objects - -#### `Package` +#### `runInBackground` -Returned by [`getPackages()`](#getpackages) and supplied in package change -events. It combines [`PackageInfo`](#packageinfo) with a `pkgId`. +Starts a Python process you control programmatically. ```typescript -interface Package extends PackageInfo { - readonly pkgId: PackageId; -} +runInBackground( + environment: PythonEnvironment, + options: PythonBackgroundRunOptions, +): Promise; ``` -#### `PackageId` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment used to start the process. | +| `options` | [`PythonBackgroundRunOptions`](#pythonbackgroundrunoptions) | Yes | `args` for Python, plus optional `cwd` and `env`. | -Identifies a package, its package manager, and its environment. +**Returns** `Promise` with `stdin`, `stdout`, `stderr`, `kill()`, +and `onExit()`. ```typescript -interface PackageId { - id: string; - managerId: string; - environmentId: string; -} +const proc = await api.runInBackground(env, { + args: ['-c', 'import sys; print(sys.version)'], + cwd: projectUri.fsPath, +}); + +proc.stdout.on('data', (chunk) => console.log(String(chunk))); +proc.stderr.on('data', (chunk) => console.error(String(chunk))); +proc.onExit((code) => console.log('exited with', code)); ``` -#### `PackageInfo` +> [!IMPORTANT] +> You own the process lifetime. Always attach an `onExit` handler and call +> `kill()` when your extension deactivates or the work is cancelled, otherwise +> processes can outlive the window. -Passed to [`createPackageItem()`](#createpackageitem) and forms the base of -every returned `Package`. +## Environment variables + +These methods resolve the variables Python should run with for a scope, +including `.env` files the extension monitors. + +### Environment variable data types + +#### `DidChangeEnvironmentVariablesEventArgs` -| Property | Type | Required | Description | +| Field | Type | Required | Description | | --- | --- | --- | --- | -| `name` | `string` | Yes | Package name. | -| `displayName` | `string` | Yes | User-facing package name. | -| `version` | `string` | No | Installed package version. | -| `description` | `string` | No | Package description. | -| `tooltip` | `string \| MarkdownString` | No | Hover text. | -| `iconPath` | `IconPath` | No | Package icon. | -| `uris` | `readonly Uri[]` | No | Files or locations associated with the package. | -| `isTransitive` | `boolean` | No | Whether the package is a transitive dependency. | +| `uri` | `Uri` | No | The file that changed. Absent when a non-file source changed. | +| `changeType` | `vscode.FileChangeType` | Yes | Whether the source was created, changed, or deleted. | -#### `GetPackagesOptions` +### Environment variable methods + +#### `getEnvironmentVariables` -Passed to [`getPackages()`](#getpackages) and `PackageManager.getPackages()`. +Resolves the effective environment variables for a scope. ```typescript -interface GetPackagesOptions { - skipCache?: boolean; -} +getEnvironmentVariables( + uri: Uri | undefined, + overrides?: ({ [key: string]: string | undefined } | Uri)[], + baseEnvVar?: { [key: string]: string | undefined }, +): Promise<{ [key: string]: string | undefined }>; ``` -Set `skipCache` to true to request current data from the underlying package -tool. +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `uri` | `Uri \| undefined` | Yes (may be `undefined`) | Project, workspace, or file to resolve variables for. `undefined` resolves the global scope. | +| `overrides` | `({ [key: string]: string \| undefined } \| Uri)[]` | No | Additional sources applied in array order. A plain object contributes its entries; a `Uri` is read as an `.env` file. | +| `baseEnvVar` | `{ [key: string]: string \| undefined }` | No | Starting set of variables. Defaults to `process.env`. | + +**Returns** `Promise<{ [key: string]: string | undefined }>` - the merged +result. An `undefined` value means the variable is unset. -#### `PackageManagementOptions` +Precedence, lowest to highest: -Passed to [`managePackages()`](#managepackages) and -`PackageManager.manage()`. At least one of `install` or `uninstall` is required. +1. `baseEnvVar` if provided, otherwise `process.env`. +2. The `.env` file named by the `python.envFile` setting for the workspace. +3. The `.env` file at the root of the Python project. +4. Each entry in `overrides`, in order. ```typescript -type PackageManagementOptions = { - runHeadless?: boolean; - upgrade?: boolean; - showSkipOption?: boolean; - install?: string[]; - uninstall?: string[]; -}; +const env = await api.getEnvironmentVariables( + projectUri, + [vscode.Uri.joinPath(projectUri, '.env.test'), { CI: '1' }], + process.env, +); ``` -The exported type uses a union to enforce the `install` or `uninstall` -requirement at compile time. `PackageManagementInteractionOptions` contributes -the optional `runHeadless` property. +#### `onDidChangeEnvironmentVariables` -#### `GetPackageAvailableVersionsOptions` - -Controls error behavior for -[`getPackageAvailableVersions()`](#getpackageavailableversions). +Fires when a monitored `.env` file or other variable source changes. ```typescript -interface GetPackageAvailableVersionsOptions { - errorMode?: 'legacy' | 'throw'; -} +onDidChangeEnvironmentVariables: Event; ``` -#### `Pep440Version` - -Represents a parsed PEP 440 package version. It is re-exported from -`@renovatebot/pep440` and returned by package tool/version lookup methods. +| Payload | Type | Description | +| --- | --- | --- | +| `e` | [`DidChangeEnvironmentVariablesEventArgs`](#didchangeenvironmentvariableseventargs) | `{ uri, changeType }` for the changed source. | -#### `DidChangePackagesEventArgs` and `PackageChangeKind` +**Returns** a `Disposable` from the subscription. Re-call +`getEnvironmentVariables` to get refreshed values; the event does not carry +them. ```typescript -interface DidChangePackagesEventArgs { - environment: PythonEnvironment; - manager: PackageManager; - changes: { kind: PackageChangeKind; pkg: Package }[]; -} - -enum PackageChangeKind { - add = 'add', - remove = 'remove', -} +context.subscriptions.push( + api.onDidChangeEnvironmentVariables(async (e) => { + console.log('env source changed', e.uri?.fsPath, e.changeType); + const refreshed = await api.getEnvironmentVariables(projectUri); + }), +); ``` -### Project objects +## Extensibility -#### `PythonProject` +Implement one of these interfaces and register it to contribute your own +environment manager, package manager, or project creator. Registration returns +a `Disposable`; dispose it on deactivation. -Returned by project lookup methods and accepted by project modification and -execution methods. +### Extensibility data types -```typescript -interface PythonProject { - readonly name: string; - readonly uri: Uri; - readonly description?: string; - readonly tooltip?: string | MarkdownString; -} -``` +#### `EnvironmentManager` -#### `PythonProjectCreatorOptions` +Discovers, creates, removes, and selects environments of one kind. The +extension calls these methods in response to UI actions, startup, terminal +activation, and API calls; treat the documented contract, not any particular +trigger, as the specification. -Passed to `PythonProjectCreator.create()`. +| Member | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Manager name. Allowed characters: `a-z`, `A-Z`, `0-9`, `-`, `_`. | +| `displayName` | `string` | No | Name shown in the UI. | +| `preferredPackageManagerId` | `string` | Yes | Package manager to pair with, formatted `.:`, for example `ms-python.python:pip`. | +| `description` | `string` | No | Secondary text shown in the UI. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the manager. | +| `iconPath` | [`IconPath`](#iconpath) | No | Icon shown for the manager. | +| `log` | `LogOutputChannel` | No | Output channel used for the manager's logs. | +| `getEnvironments(scope)` | `(scope: GetEnvironmentsScope) => Promise` | Yes | Returns the environments known for the scope. Called frequently by UI surfaces. | +| `refresh(scope)` | `(scope: RefreshEnvironmentsScope) => Promise` | Yes | Re-discovers environments for the scope. | +| `set(scope, environment?)` | `(scope: SetEnvironmentScope, environment?: PythonEnvironment) => Promise` | Yes | Sets or clears the active environment for the scope. Also called at startup to rehydrate persisted state. | +| `get(scope)` | `(scope: GetEnvironmentScope) => Promise` | Yes | Returns the active environment for the scope. Called very frequently. | +| `resolve(context)` | `(context: ResolveEnvironmentContext) => Promise` | Yes | Turns a `Uri` for an interpreter or environment folder into a fully populated environment with complete `execInfo`. | +| `create(scope, options?)` | `(scope: CreateEnvironmentScope, options?: CreateEnvironmentOptions) => Promise` | No | Creates an environment. Omit the method entirely if creation is unsupported - the UI disables create when `create === undefined`. Add a `.gitignore` when creating a folder inside the workspace. | +| `remove(environment, options?)` | `(environment: PythonEnvironment, options?: RemoveEnvironmentOptions) => Promise` | No | Deletes an environment. | +| `quickCreateConfig()` | `() => QuickCreateConfig \| undefined` | No | Describes the quick create path. Implementing it enables quick create, which requires `create` too. | +| `clearCache()` | `() => Promise` | No | Drops cached environment data so later calls re-discover from disk. | +| `onDidChangeEnvironments` | `Event` | No | Fire when discovery results change. | +| `onDidChangeEnvironment` | `Event` | No | Fire when the manager's active environment changes. | + +```typescript +class MyEnvManager implements EnvironmentManager { + readonly name = 'my-manager'; + readonly displayName = 'My Manager'; + readonly preferredPackageManagerId = 'ms-python.python:pip'; + + constructor(private readonly api: PythonEnvironmentApi) {} + + async getEnvironments( + scope: GetEnvironmentsScope, + ): Promise { + const found = await discover(scope); + return found.map((info) => + this.api.createPythonEnvironmentItem(info, this), + ); + } -```typescript -interface PythonProjectCreatorOptions { - name: string; - rootUri: Uri; - quickCreate?: boolean; + async refresh(scope: RefreshEnvironmentsScope): Promise {} + async set( + scope: SetEnvironmentScope, + environment?: PythonEnvironment, + ): Promise {} + async get( + scope: GetEnvironmentScope, + ): Promise { + return undefined; + } + async resolve( + context: ResolveEnvironmentContext, + ): Promise { + return undefined; + } } ``` -#### `DidChangePythonProjectsEventArgs` +#### `PackageManager` -Passed to [`onDidChangePythonProjects`](#ondidchangepythonprojects). +Reports and changes the packages of an environment. -```typescript -interface DidChangePythonProjectsEventArgs { - added: PythonProject[]; - removed: PythonProject[]; -} -``` +| Member | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Manager name. Allowed characters: `a-z`, `A-Z`, `0-9`, `-`, `_`. | +| `displayName` | `string` | No | Name shown in the UI. | +| `description` | `string` | No | Secondary text shown in the UI. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the manager. | +| `iconPath` | [`IconPath`](#iconpath) | No | Icon shown for the manager. | +| `log` | `LogOutputChannel` | No | Output channel used for the manager's logs. | +| `manage(environment, options)` | `(environment: PythonEnvironment, options: PackageManagementOptions) => Promise` | Yes | Installs and uninstalls the requested packages. | +| `refresh(environment)` | `(environment: PythonEnvironment) => Promise` | Yes | Re-reads the installed package list. | +| `getPackages(environment, options?)` | `(environment: PythonEnvironment, options?: GetPackagesOptions) => Promise` | Yes | Returns installed packages, or `undefined` if they cannot be retrieved. | +| `getPackageWatchTargets(environment)` | `(environment: PythonEnvironment) => RelativePattern[]` | No | Extra filesystem patterns to watch for install and uninstall changes, appended to the default site-packages locations. Implement for manager-specific locations such as `conda-meta`. | +| `getDirectPackageNames(environment)` | `(environment: PythonEnvironment) => Promise \| undefined>` | No | Best-effort set of non-transitive package names. Most tools cannot record user intent - pip uses `pip list --not-required`, which reports leaf packages rather than explicitly installed ones. | +| `clearCache()` | `() => Promise` | No | Drops cached package data. | +| `getVersion(environment)` | `(environment: PythonEnvironment) => Promise` | No | Version of the underlying tool, such as pip, uv, or conda. | +| `getPackageAvailableVersions(environment, packageName)` | `(environment: PythonEnvironment, packageName: string) => Promise` | No | Available versions, newest first. Throw `PackageVersionLookupNotSupportedError` when unsupported and let operational failures propagate. Resolving to `undefined` is treated as unsupported. | +| `formatInstallSpec(packageName, version)` | `(packageName: string, version: string) => string` | No | Formats a pinned specifier for this tool, for example `requests==2.31.0` for pip or `requests=2.31.0` for conda. Callers default to `name==version` when absent. | +| `onDidChangePackages` | `Event` | No | Fire when packages change. | + +```typescript +class MyPackageManager implements PackageManager { + readonly name = 'my-pm'; + + constructor(private readonly api: PythonEnvironmentApi) {} + + async manage( + environment: PythonEnvironment, + options: PackageManagementOptions, + ): Promise { + if (options.install?.length) { + /* install */ + } + if (options.uninstall?.length) { + /* uninstall */ + } + } -### Execution objects + async refresh(environment: PythonEnvironment): Promise {} -#### `PythonTerminalCreateOptions` + async getPackages( + environment: PythonEnvironment, + ): Promise { + const infos = await readInstalled(environment); + return infos.map((info) => + this.api.createPackageItem(info, environment, this), + ); + } -Passed to [`createTerminal()`](#createterminal). It includes all VS Code -`TerminalOptions` and adds: + async getPackageAvailableVersions( + environment: PythonEnvironment, + packageName: string, + ): Promise { + throw new PackageVersionLookupNotSupportedError(); + } -```typescript -interface PythonTerminalCreateOptions extends TerminalOptions { - disableActivation?: boolean; + formatInstallSpec(packageName: string, version: string): string { + return `${packageName}==${version}`; + } } ``` -#### `PythonTerminalExecutionOptions` +#### `PythonProjectCreator` -Passed to [`runInTerminal()`](#runinterminal) and -[`runInDedicatedTerminal()`](#runindedicatedterminal). +Contributes a project-creation flow, such as a template or scaffolding wizard. -```typescript -interface PythonTerminalExecutionOptions { - cwd: string | Uri; - args?: string[]; - show?: boolean; -} -``` +| Member | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | `string` | Yes | Creator name. | +| `displayName` | `string` | No | Name shown in the creation picker. | +| `description` | `string` | No | Secondary text shown in the picker. | +| `tooltip` | `string \| MarkdownString` | No | Hover text for the creator. | +| `supportsQuickCreate` | `boolean` | No | `true` when the creator can run with no user input. | +| `create(options?)` | `(options?: PythonProjectCreatorOptions) => Promise` | Yes | Creates the project or files. Return `PythonProject`(s) for real projects, `Uri`(s) for files that do not constitute a project, or `undefined` if creation fails or is cancelled. | -#### `PythonTaskExecutionOptions` +### Extensibility methods -Passed to [`runAsTask()`](#runastask). +#### `registerEnvironmentManager` ```typescript -interface PythonTaskExecutionOptions { - name: string; - args: string[]; - project?: PythonProject; - cwd?: string; - env?: { [key: string]: string }; -} +registerEnvironmentManager( + manager: EnvironmentManager, + options?: { extensionId?: string }, +): Disposable; ``` -#### `PythonBackgroundRunOptions` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `manager` | [`EnvironmentManager`](#environmentmanager) | Yes | The manager implementation to register. | +| `options` | `{ extensionId?: string }` | No | Registration options. | +| `options.extensionId` | `string` | No | Extension ID of the calling extension. Detected automatically when omitted or not found. | -Passed to [`runInBackground()`](#runinbackground). +**Returns** `Disposable` that unregisters the manager. ```typescript -interface PythonBackgroundRunOptions { - args: string[]; - cwd?: string; - env?: { [key: string]: string | undefined }; -} +context.subscriptions.push( + api.registerEnvironmentManager(new MyEnvManager(api)), +); ``` -#### `PythonProcess` - -Returned by [`runInBackground()`](#runinbackground). +#### `registerPackageManager` ```typescript -interface PythonProcess { - readonly pid?: number; - readonly stdin: NodeJS.WritableStream; - readonly stdout: NodeJS.ReadableStream; - readonly stderr: NodeJS.ReadableStream; - - kill(): void; - onExit( - listener: ( - code: number | null, - signal: NodeJS.Signals | null, - ) => void, - ): void; -} +registerPackageManager( + manager: PackageManager, + options?: { extensionId?: string }, +): Disposable; ``` -### Environment variable objects - -#### `DidChangeEnvironmentVariablesEventArgs` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `manager` | [`PackageManager`](#packagemanager) | Yes | The package manager implementation to register. | +| `options` | `{ extensionId?: string }` | No | Registration options. | +| `options.extensionId` | `string` | No | Extension ID of the calling extension. Detected automatically when omitted or not found. | -Passed to -[`onDidChangeEnvironmentVariables`](#ondidchangeenvironmentvariables). +**Returns** `Disposable` that unregisters the manager. ```typescript -interface DidChangeEnvironmentVariablesEventArgs { - uri?: Uri; - changeType: FileChangeType; -} +context.subscriptions.push( + api.registerPackageManager(new MyPackageManager(api)), +); ``` -`uri` is absent for a non-file source. `changeType` is VS Code's -`FileChangeType`. - -### Shared UI objects - -#### `IconPath` +To pair your package manager with your environment manager, set +`preferredPackageManagerId` on the environment manager to +`.:`. -Used by environment, package, group, and provider display objects. +#### `registerPythonProjectCreator` ```typescript -type IconPath = - | Uri - | { - light: Uri; - dark: Uri; - } - | ThemeIcon; +registerPythonProjectCreator(creator: PythonProjectCreator): Disposable; ``` -#### `EnvironmentGroupInfo` +| Parameter | Type | Required | Description | +| --- | --- | --- | --- | +| `creator` | [`PythonProjectCreator`](#pythonprojectcreator) | Yes | The project creator implementation to register. | -Provides display information for an environment group. +**Returns** `Disposable` that unregisters the creator. ```typescript -interface EnvironmentGroupInfo { - readonly name: string; - readonly description?: string; - readonly tooltip?: string | MarkdownString; - readonly iconPath?: IconPath; -} +context.subscriptions.push( + api.registerPythonProjectCreator({ + name: 'my-template', + displayName: 'My Project Template', + supportsQuickCreate: true, + async create(options) { + if (!options) { + return undefined; + } + await scaffold(options.rootUri, options.name); + return { name: options.name, uri: options.rootUri }; + }, + }), +); ``` -When several group definitions use the same name, the first instance is used -in the UI. - -### API interface groups +## API interface groups -The interfaces below organize the flat API for type composition. They do not -represent nested runtime objects. +`PythonEnvironmentApi` is assembled from the interfaces below. They exist to +organize the TypeScript declarations only - at runtime every member lives +directly on the single flat API object. | Interface | Members grouped by the interface | Description | | --- | --- | --- | @@ -1550,9 +1757,9 @@ represent nested runtime objects. - Declare `ms-python.vscode-python-envs` in `extensionDependencies`. - Acquire the API through `PythonEnvironments.api()`. - Remember that the API object is flat. -- Use `env.envId`, not a direct `env.id`. +- Use `env.envId` and `pkg.pkgId`, not `env.id` or `pkg.id`. - Pass URIs so the API can route project and environment operations. -- Feature-detect optional provider methods. +- Feature-detect optional provider methods before calling them. - Dispose event subscriptions and provider registrations. - Preserve operational errors and handle documented `undefined` results. - Use `isPackageVersionLookupNotSupportedError()` across bundle boundaries. From 507bee2ca150e53e43b4146535e1de48dcbaef3e Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Fri, 18 Sep 2026 13:02:11 -0700 Subject: [PATCH 6/6] docs: address PR review feedback on API manual Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f79caeb-d789-4eb6-898b-4bddd9d3292a --- api/README.md | 17 +++++++++++++++++ docs/README.md | 49 +++++++++++++++++++++++++++++++++++++++++++------ src/types.ts | 4 +++- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/api/README.md b/api/README.md index b7c96bac..c3262bbf 100644 --- a/api/README.md +++ b/api/README.md @@ -35,3 +35,20 @@ export async function activate() { } ``` +## Full API reference + +📘 **[Python Environments API reference](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md)** + +The complete manual documents every method and data type, organized by domain - +environments, packages, projects, execution, environment variables, and +extensibility - with field tables, parameter tables, return types, and examples. + +- [Environments](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#environments) - discover, resolve, select, create, and remove interpreters +- [Packages](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#packages) - list, install, uninstall, and look up versions +- [Projects](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#projects) - the folders the extension tracks +- [Execution](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#execution) - run Python in terminals, tasks, and background processes +- [Environment variables](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#environment-variables) - resolved variables for a scope +- [Extensibility](https://github.com/microsoft/vscode-python-environments/blob/main/docs/README.md#extensibility) - register your own environment manager, package manager, or project creator + +See [`CHANGELOG.md`](https://github.com/microsoft/vscode-python-environments/blob/main/api/CHANGELOG.md) for API changes between versions. + diff --git a/docs/README.md b/docs/README.md index e98e3caf..0d4c1e86 100644 --- a/docs/README.md +++ b/docs/README.md @@ -379,12 +379,17 @@ refreshEnvironments(scope: RefreshEnvironmentsScope): Promise; | --- | --- | --- | --- | | `scope` | [`RefreshEnvironmentsScope`](#scope-types) | Yes (may be `undefined`) | `Uri` refreshes discovery for that project or folder; `undefined` refreshes global and workspace discovery. | -**Returns** `Promise`, resolving when discovery completes. Results arrive -through [`onDidChangeEnvironments`](#ondidchangeenvironments); subscribe before -refreshing if you need the deltas. +**Returns** `Promise`, resolving when the managers finish discovery. + +Read the results with [`getEnvironments`](#getenvironments) once the promise +settles. Do not rely on [`onDidChangeEnvironments`](#ondidchangeenvironments) +to deliver them: that event is optional on `EnvironmentManager`, and +`refreshEnvironments` does not synthesize one, so whether a refresh produces +deltas is up to the provider. ```typescript await api.refreshEnvironments(undefined); +// Authoritative: read the list rather than waiting for an event. const refreshed = await api.getEnvironments('all'); ``` @@ -439,6 +444,29 @@ const active = await api.getEnvironment( ); ``` +> [!IMPORTANT] +> **This call can return a stale value.** It is deliberately non-blocking: it +> races the real resolution against a one-second timeout so that slow initial +> discovery cannot stall callers. If resolution has not finished in time, it +> returns the *last-known* environment for the scope - which may be `undefined` +> on a first call - while resolution continues in the background. +> +> The resolved value is published through +> [`onDidChangeEnvironment`](#ondidchangeenvironment) once it settles. If your +> feature needs the authoritative selection, subscribe to that event and treat +> the value from `getEnvironment` as a fast first guess: +> +> ```typescript +> let current = await api.getEnvironment(projectUri); // May be last-known. +> context.subscriptions.push( +> api.onDidChangeEnvironment((e) => { +> if (e.uri?.toString() === projectUri.toString()) { +> current = e.new; // Authoritative once resolution settles. +> } +> }), +> ); +> ``` + #### `setEnvironment` Selects - or clears - the environment for one or more scopes, and persists the @@ -1236,7 +1264,7 @@ const module: PythonTerminalExecutionOptions = { | `name` | `string` | Yes | Name of the task, shown in the task UI. | | `args` | `string[]` | Yes | Arguments passed to the Python executable. | | `project` | [`PythonProject`](#pythonproject) | No | Project the task belongs to. | -| `cwd` | `string` | No | Working directory. Defaults to the project directory of the script being run. | +| `cwd` | `string` | No | Working directory for the task's shell execution. When omitted, VS Code resolves it from the task scope - the workspace folder containing `project`, or the global scope when `project` is not supplied. | | `env` | `{ [key: string]: string }` | No | Additional environment variables for the task. | #### `PythonBackgroundRunOptions` @@ -1244,7 +1272,7 @@ const module: PythonTerminalExecutionOptions = { | Field | Type | Required | Description | | --- | --- | --- | --- | | `args` | `string[]` | Yes | Arguments passed to the Python executable. | -| `cwd` | `string` | No | Working directory. Defaults to the project directory of the script being run. | +| `cwd` | `string` | No | Working directory, passed straight to the spawned process. When omitted, the process inherits the extension host's working directory, which is **not** your project folder - always supply `cwd` (for example `project.uri.fsPath`) if the script resolves relative paths. | | `env` | `{ [key: string]: string \| undefined }` | No | Additional environment variables. An `undefined` value unsets a variable. | #### `PythonProcess` @@ -1391,11 +1419,20 @@ runInBackground( | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `environment` | [`PythonEnvironment`](#pythonenvironment) | Yes | Environment used to start the process. | -| `options` | [`PythonBackgroundRunOptions`](#pythonbackgroundrunoptions) | Yes | `args` for Python, plus optional `cwd` and `env`. | +| `options` | [`PythonBackgroundRunOptions`](#pythonbackgroundrunoptions) | Yes | `args` for Python, plus optional `cwd` and `env`. Supply `cwd` - it is not inferred. | **Returns** `Promise` with `stdin`, `stdout`, `stderr`, `kill()`, and `onExit()`. +> [!IMPORTANT] +> `cwd` is forwarded to the spawned process unchanged. There is no project +> context to infer it from, so when you omit it the process inherits the +> extension host's working directory rather than your project folder. Pass +> `cwd` explicitly whenever the script resolves relative paths. +> +> You own the process lifetime: call `kill()` when your feature is done, and +> tie it to your disposables so it does not outlive deactivation. + ```typescript const proc = await api.runInBackground(env, { args: ['-c', 'import sys; print(sys.version)'], diff --git a/src/types.ts b/src/types.ts index 50ceff79..12c93df9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1423,7 +1423,9 @@ export interface PythonBackgroundRunOptions { args: string[]; /** - * Current working directory for the script or module. Default is the project directory for the script being run. + * Current working directory for the script or module. This is passed directly to the spawned + * process; when it is omitted the process inherits the extension host's working directory, + * which is not the project directory. Supply this when the script resolves relative paths. */ cwd?: string;