Skip to content

Commit 39ba576

Browse files
committed
Merge remote-tracking branch 'origin/main' into api-2-manager-capabilities
# Conflicts: # src/managers/builtin/sysPythonManager.ts # src/managers/poetry/poetryManager.ts
2 parents c16e242 + 9ee9e22 commit 39ba576

88 files changed

Lines changed: 8853 additions & 972 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/instructions/testing-workflow.instructions.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ This guide covers the full testing lifecycle:
2020

2121
- Pip commands that return JSON must pass `--disable-pip-version-check`; the process helper combines stderr with stdout, so update notices can otherwise make valid JSON unparseable (1).
2222
- When a view subscribes to a newly added provider event, TypeMoq-based view tests must return a real `EventEmitter.event`; an unstubbed event yields an undefined disposable and fails during teardown (1).
23+
- Test agent selections across a full reload with `python.defaultInterpreterPath` set. An effective manager value equal to the extension default does not prove a workspace value was saved; tool-owned persistence must inspect `workspaceValue` or startup can restore the global interpreter (1).
24+
- `vscode.executeCodeLensProvider` verifies provider output, not visible editor refresh. VS Code cancels its debounced CodeLens refresh when the editor loses focus; account for background UI automation restoring focus to another app before diagnosing a stale rendered lens (1).
2325

2426
### When to Use This Guide
2527

‎CONTRIBUTING.md‎

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,152 @@ The npm package under [`api/`](./api) is the public API facade other extensions
9898
- `src/api.ts`, `src/types.ts`, and `src/publicErrors.ts` are validated on every PR by the extension's own lint and TypeScript compile.
9999
- **Versioning and compatibility:** the published package version in [`api/package.json`](./api/package.json) is maintained independently of the extension version in [`package.json`](./package.json) — the two do not need to match. Compatibility is based on the API shape exported by the installed Python Environments extension at runtime. Package updates must preserve backwards-compatible contracts unless the API package version intentionally communicates a breaking change; consumers should treat newly added members as optional when they may run against older installed extension versions. Any PR that edits `src/api.ts`, `src/types.ts`, or `src/publicErrors.ts` must bump `api/package.json` (use the `skip api version` label to bypass) and add an entry to [`api/CHANGELOG.md`](./api/CHANGELOG.md) (use the `skip api changelog` label to bypass).
100100

101+
## Internal Python agent-tool bridge
102+
103+
The extension's existing **flat** export also provides `__pythonTools.version === 1`.
104+
This is an internal convention, not a security boundary or a supported public API.
105+
Its types live only in `src/internal/pythonToolsApi.ts`; do not copy them into
106+
`src/types.ts`, `src/api.ts`, or the published API package. Consumers must check the
107+
version and all three methods at runtime before using the private API. The Python
108+
consumer uses its previous public Environments integration when that capability
109+
is absent or incompatible; that compatibility route can still prompt. It never
110+
falls back after a private operation starts, including errors, partial results,
111+
timeouts, or cancellation. Environments-disabled users retain the legacy Python
112+
route.
113+
Explicit compatibility selection persists through the public Environments setter, not
114+
just the Python extension's cached interpreter path, so later package operations
115+
use the same environment. Reusing an already-selected environment is read-only;
116+
it does not reselect or wait for an interpreter-change event.
117+
118+
| Method | Request | Result |
119+
| --- | --- | --- |
120+
| `configureEnvironment(request, token)` | `{ resourcePath?: string, pythonPath?: string }` | Select an explicit interpreter exactly; otherwise reuse an isolated environment or create one for the project. Success includes `created`. |
121+
| `getEnvironment(request, token)` | `{ resourcePath?: string, includePackages?: boolean }` | Resolve the real selected environment, not the public API's short-timeout cache. `includePackages: true` returns `{ name, version?: string }[]`, including `[]`, or an error. |
122+
| `installPackages(request, token)` | `{ resourcePath?: string, packages: string[] }` | Install into an isolated environment. Does not create or change selection implicitly, or modify any global/base interpreter. |
123+
124+
Automatic configuration uses isolation by default, regardless of whether a global
125+
interpreter was selected automatically or by a person. Virtual environments are
126+
recognized by `pyvenv.cfg`; non-base Conda and inline-script environments are also
127+
isolated. Existing isolated environments outside the workspace remain reusable.
128+
When no isolated environment is selected, configuration can resolve Poetry/Pipenv
129+
caches through the native finder's project association. It does not infer ownership
130+
from the cache location or record selection history; ambiguous project matches
131+
require an explicit `pythonPath`. Read-only queries do not select these candidates.
132+
There is no selection-origin bookkeeping or persistence. Old origin records are
133+
ignored; ordinary current-interpreter selection and its persistence are unchanged.
134+
An explicit `pythonPath` selects that interpreter without creating an environment.
135+
This can select global Python for execution or queries, but does not authorize
136+
package installation into it: `ENVIRONMENT_NOT_ISOLATED` directs the caller to
137+
configure without `pythonPath` or select an existing isolated environment.
138+
139+
All methods require a VS Code `CancellationToken` and a trusted workspace.
140+
`resourcePath` accepts an absolute path or a `file:` URI within an open workspace.
141+
The Python tools default an omitted or empty path to the first workspace folder and
142+
pass it explicitly. They disclose the target in preparation/results and instruct agents
143+
to reuse the returned `resourcePath`. Editor focus does not change this default.
144+
At the direct private API boundary, omission selects the only workspace root;
145+
zero/multiple roots still produce actionable errors. The consumer default does not
146+
relax that backend validation.
147+
Other explicit input is never replaced by that default. Because the route is
148+
workspace scoped, read-only tools keep their previous Environments behaviour when
149+
no folder is open. Configuration and installation with a compatible private API
150+
report `NO_WORKSPACE` instead of opening a picker or modifying global Python.
151+
Nested projects and per-script selections retain their scope; a file is
152+
never used as an environment creation directory. There is no User/global settings
153+
fallback. An explicit `pythonPath` can switch away from an unavailable manager.
154+
New nested targets persist as exact project entries without changing root defaults;
155+
their automatic dependency discovery is restricted to that target directory.
156+
Tool selections at the workspace root persist an explicit workspace manager, even
157+
when it matches the extension default. This prevents `python.defaultInterpreterPath`
158+
from restoring global Python on reload without rewriting that interpreter setting.
159+
Ordinary human selections retain their existing settings-write behavior.
160+
161+
Results are `{ status: 'success', environment, resourcePath?, created?, packages? }`
162+
or `{ status: 'error', code, message, environment?, resourcePath? }`. Errors retain
163+
an already-created environment when subsequent package installation or selection
164+
fails; retries reuse a resolvable `.venv`/`.conda` rather than creating a suffixed
165+
directory. Selecting an existing environment does not reinstall project
166+
dependencies. A resolvable `.venv`/`.conda` is only reused when it actually contains
167+
the resolved isolated environment. A new subdirectory can reuse an isolated parent-project
168+
selection; `resourcePath` chooses the target, not a requirement to create a fresh
169+
environment.
170+
Register an independent project when it needs a separate selection.
171+
Reads and writes for the same effective target share a cancellable
172+
queue; waiting behind an operation does not use the discovery timeout. Different
173+
targets remain independent. Discovery timeouts report `NOT_READY`; a configured
174+
environment manager that never registers reports `UNSUPPORTED_MANAGER` naming the id;
175+
subprocess
176+
timeouts report `TIMEOUT`. Cancellation throws `CancellationError` after owned
177+
process cleanup. Cancellation is cooperative, not a rollback: completed writes or
178+
package changes are not undone. A completed action remains successful if a
179+
cancellation request arrives on its return. If cleanup fails,
180+
`PROCESS_TERMINATION_FAILED` takes precedence:
181+
the process may still be modifying files, so consumers must not blindly retry.
182+
Cleanup waits for process closure and owned POSIX group completion; an exited
183+
Windows parent cannot turn a failed tree termination into successful cancellation.
184+
If a Windows parent has already exited when cancellation starts, descendant
185+
cleanup cannot be confirmed; the tool reports that uncertainty instead of
186+
signalling a potentially reused PID.
187+
Only internal `toolExecution` calls enable these subprocess policies. Public
188+
progress tokens do not enable agent timeouts, input suppression, or process-tree
189+
cleanup semantics. Public package calls retain their existing execution defaults.
190+
191+
Internal symbol capabilities opt built-in managers into prompt-free operations;
192+
they are unrelated to the human UI's `quickCreateConfig`. Creation supports venv,
193+
Conda, and enabled PEP 723 inline scripts (the latter retain their existing cache
194+
ownership/metadata rules). System and Pyenv base interpreters route creation to
195+
venv; Conda base routes to an isolated Conda prefix. Poetry and Pipenv environments
196+
can be reused, but unsupported creation and contributed managers without this
197+
capability fail explicitly. Private package operations use the existing headless pip/uv, Conda, and
198+
Poetry helpers with the real token and strict, uncached package listing. Venv
199+
dependency validation fails rather than asking to continue. No tool installs a
200+
base Python automatically. Conda response descriptors clone `execInfo.activatedRun`
201+
to a standalone `conda run --prefix ... --no-capture-output ...` command; shared
202+
public descriptors are not changed.
203+
Venv creation uses the selected Python 3 as its base, falling back to the latest
204+
discovered Python 3 only when none is selected. Conda creation requests the selected
205+
Python major/minor version. This does not add a project version-constraint resolver
206+
for `pyproject.toml` or `.python-version`. To choose a particular base, select it
207+
first and then configure without `pythonPath`; the path parameter itself is an
208+
exact selection request, not a venv base-interpreter argument.
209+
Bare Conda commands configured through `python.condaPath` are resolved through
210+
PATH for private execution and returned command prefixes.
211+
Conda base is isolated even when inherited through `CONDA_PREFIX`, and failed
212+
Conda discovery does not authorize replacement creation. Public Conda refresh remains best-effort while
213+
the private readiness check retains the discovery error. Poetry verifies the
214+
requested project's actual environment prefix before package operations. Agent
215+
inventory uses `poetry run pip list --format=json`, including when Poetry supplies
216+
pip itself; the public lockfile-based `poetry show` path is unchanged.
217+
Private Poetry installs also apply the lockfile with `install --no-root`, since
218+
`add` alone skips already-declared dependencies missing from the environment.
219+
Direct installs into shared PEP 723
220+
caches return `IMMUTABLE_ENVIRONMENT`; edit script metadata and configure again.
221+
With the inline-script feature enabled, configuring a Python file with PEP 723
222+
metadata can create its cache on the first tool invocation, without first using a
223+
CodeLens or registering a script project manually. Invalid metadata fails rather
224+
than falling back to ordinary project creation. This opt-in configuration route
225+
does not change human or read-only routing, which still requires a validated
226+
association. Target the project directory or supply `pythonPath` for ordinary
227+
project/exact-interpreter configuration instead.
228+
An agent joining a human-owned inline cache build gets `ENVIRONMENT_BUSY` without
229+
waiting for or cancelling the human operation.
230+
231+
Private discovery is separate from public initialization and its onboarding.
232+
Agent reads never start or await installation or missing-manager questions, even
233+
when a concurrent public initialization is waiting for input. Ordinary startup,
234+
public API, and human workflows retain their prompts; independently triggered
235+
startup UI can still appear while a tool runs. There is no global "suppress UI"
236+
switch. Exact nested-project persistence is also opt-in to the private setter;
237+
ordinary manager-setting updates retain their existing defaults.
238+
Human venv initialization retries discovery and base preparation after a failure,
239+
including failures after discovery has completed. A partial initialization is not
240+
reused as a successful cache on the next public lookup; private discovery still
241+
does not wait for human onboarding.
242+
243+
Keep tests for both internal and human routes when changing these helpers. The
244+
focused suites are in `src/test/internal`, with shared creation/package and
245+
inline-script regressions under `src/test/managers`.
246+
101247
## Questions or Issues?
102248

103249
- **Questions**: Start a [discussion](https://github.com/microsoft/vscode-python/discussions/categories/q-a)

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ All commands can be accessed via the Command Palette (`ctrl/cmd + Shift + P`):
130130
| pythonProjects | `[]` | A list of Python workspaces, specified by the path, in which you can set particular environment and package managers. You can set information for a workspace as `[{"path": "/path/to/workspace", "envManager": "ms-python.python:venv", "packageManager": "ms-python.python:pip"]}`. |
131131
| terminal.showActivateButton | `false` | (experimental) Show a button in the terminal to activate/deactivate the current environment for the terminal. This button is only shown if the active terminal is associated with a project that has an activatable environment. |
132132
| terminal.autoActivationType | `"command"` | Specifies how the extension can activate an environment in a terminal. Accepted values: `command` (execute activation command in terminal), `shellStartup` (`terminal.integrated.shellIntegration.enabled` successfully enabled or we may modify shell startup scripts ), `off` (no auto-activation). Shell startup is only supported for: zsh, fish, pwsh, bash, cmd. **Takes precedence over** `python.terminal.activateEnvironment`. Restart terminals after changing this setting. To revert shell startup changes, run `Python Envs: Revert Shell Startup Script Changes`. |
133-
| alwaysUseUv | `true` | When `true`, [uv](https://github.com/astral-sh/uv) will be used to manage all virtual environments if available. When `false`, uv will only manage virtual environments explicitly created by uv. The extension prefers `uv` on its PATH; if unavailable in a trusted workspace, it also checks that workspace's `.pyprojectx/main/uv` (`uv.exe` on Windows) for environments inside that workspace. External environments still require uv on PATH. This does not alter the integrated terminal's PATH. |
133+
| alwaysUseUv | `true` | When `true`, [uv](https://github.com/astral-sh/uv) will be used to manage all virtual environments if available. When `false`, uv will only manage virtual environments explicitly created by uv. The extension uses `uv` on its PATH, or the executable it installed during the current session. If unavailable in a trusted workspace, it also checks that workspace's `.pyprojectx/main/uv` (`uv.exe` on Windows) for environments inside that workspace. External environments require uv on PATH or a current-session installation. This does not alter the integrated terminal's PATH. |
134134
| globalSearchPaths | `[]` | Global search paths for Python environments. Array of absolute directory paths to search for environments at the user level. This setting is merged with the legacy `python.venvPath` and `python.venvFolders` settings. |
135135
| workspaceSearchPaths | `[]` | Workspace search paths for Python environments. Can be absolute paths or relative directory paths searched within the workspace. |
136136

‎docs/managing-python-projects.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ When you create a script, the extension generates a single `.py` file with PEP 7
101101

102102
An inline-script environment is built from the script's `# /// script` block and stored in the extension's cache, where it is shared by every script with the same dependencies and base interpreter. Because editing one would silently change the others, these environments are not user-managed: the Python Environments views do not offer install, uninstall, or version-change actions for them. Their package list remains visible.
103103

104-
A CodeLens above the `# /// script` block offers **Set up environment for this script**, and the same action is available as a quick fix on an unresolved import. For a few seconds after setup succeeds it is replaced by a **Script environment ready (Python X.Y.Z)** confirmation naming the Python that was selected — useful when `requires-python` matches several installed versions, or when one was installed on demand. The confirmation is plain text rather than a clickable action, and it expires on its own; at every other time the setup CodeLens behaves exactly as before.
104+
A CodeLens stays above the `# /// script` block, including while its metadata is incomplete, malformed, or unsaved. It offers **Set up environment for this script** until the block matches a validated environment, then displays **Script environment ready (Python X.Y.Z)** as persistent, non-clickable text. The ready label also appears for environments restored after reopening VS Code.
105+
106+
The CodeLens follows the live block text: editing the block offers setup immediately without requiring a save, while editing Python code outside it does not change its fingerprint. Diagnostics continue to explain malformed metadata as you type; a warning popup appears only if you try to set up an invalid block. Setup saves valid unsaved changes before creating the environment. Execution and interpreter routing still use validated, saved metadata, and saving can restore a matching existing association without rebuilding it.
105107

106108
Setup records which distributions it installed. If that record and the environment's contents later disagree — for example after installing a package into it from a terminal — every script sharing the environment needs setup again. Saving or reopening a script does not repair it; use the script's setup action to rebuild from its declared dependencies.
107109

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-python-envs",
33
"displayName": "Python Environments",
44
"description": "Provides a unified python environment experience",
5-
"version": "1.39.0",
5+
"version": "1.41.0",
66
"publisher": "ms-python",
77
"engines": {
88
"vscode": "^1.110.0-20260204"

0 commit comments

Comments
 (0)