Skip to content

Use exact per-file environments for Python files (PEP 723 PR 19) - #26129

Merged
Stella Huang (StellaHuang95) merged 3 commits into
microsoft:mainfrom
StellaHuang95:copilot/pep723-pr19
Aug 31, 2026
Merged

Use exact per-file environments for Python files (PEP 723 PR 19)#26129
Stella Huang (StellaHuang95) merged 3 commits into
microsoft:mainfrom
StellaHuang95:copilot/pep723-pr19

Conversation

@StellaHuang95

@StellaHuang95 Stella Huang (StellaHuang95) commented Aug 28, 2026

Copy link
Copy Markdown

Part of microsoft/vscode-python-environments#1602. Design doc: microsoft/vscode-python-environments#1601.

Roadmap context

This is cross-repository PR 19 in the PEP 723 roadmap. It closes the Python extension's per-file lookup gaps for debugging and Pylance configuration.

Cross-repository integration PR Status
Environments PR 7: persisted script associations merged (#1697)
Environments PR 10: exact script projects microsoft/vscode-python-environments#1744
PR 17: Pylance per-file Python path lookup microsoft/pyrx#9265
PR 19: exact Python-file lookup and debugger resolution this PR

Why this PR

IInterpreterService.getActiveInterpreter(resource) normally shares in-flight, timeout, and last-known state by workspace folder. A file URI can therefore receive the workspace interpreter when a workspace lookup is already running or when exact environment resolution exceeds the timeout.

That breaks two per-file consumers:

  • debugger launch resolution can select the workspace interpreter instead of the launch program's interpreter;
  • Pylance's file-scoped workspace/configuration request can receive a cached workspace interpreter.

The exact lookup must also avoid publishing a file interpreter as a workspace-wide interpreter change.

What this PR does

  • Adds an internal exactResource option to IInterpreterService.
  • Bypasses workspace-keyed in-flight, timeout, and last-known state for exact environment-extension lookups.
  • Suppresses workspace-level interpreter-change reporting for those silent exact reads.
  • Resolves debugger programs from:
    • absolute paths;
    • ${file};
    • ${workspaceFolder};
    • ${workspaceFolder:name}.
  • Prefers the program interpreter and falls back to the normal workspace interpreter only when no exact environment is available.
  • Reuses the selected interpreter for both legacy pythonPath and command-valued python.
  • Applies activation variables when the program interpreter differs from the workspace interpreter.
  • Uses exact lookup only for .py-scoped Pylance configuration requests; workspace-level requests retain the existing cached fast path.

Lookup semantics

Condition Behavior
No concrete launch program Preserve workspace lookup
Exact program environment exists Use it for debugger resolution
Exact program lookup returns no environment Fall back to workspace interpreter
Program and workspace interpreters match Preserve existing terminal activation behavior
Program interpreter differs Apply its activation variables
Pylance requests python config for a .py URI Resolve the exact file environment
Pylance requests workspace-level config Preserve normal workspace caching
Exact lookup resolves an environment Do not publish a false workspace interpreter-change event

Performance and safety

  • The existing fast workspace cache remains unchanged for normal consumers.
  • Exact lookup is opt-in and used only by debugger program selection and .py configuration scopes.
  • The environments extension's own URI-scoped timeout/last-known behavior remains in effect.
  • No public Python or environments API is changed.

User impact

Users without a per-file environment retain the same interpreter and debugger behavior. When a Python file has a distinct environment, Pylance configuration and debugger launch consistently use that file's interpreter rather than a workspace-cached value.

Tests

  • Prettier check for all changed files
  • ESLint for all changed files
  • Focused middleware, resolver, launch, environment-adapter, and interpreter-service tests: 181 passing, 3 pending
  • Repository-wide TypeScript compilation currently also reports two existing TelemetryReporter import errors in untouched files.
  • The full unit command was run; its failures were confined to untouched platform/path, terminal activation, activated-environment, and native-finder tests.

Scope and follow-up

This PR does not implement Pylance's open-file rerouting notification. Live movement and reanalysis after a per-file environment change remain in PR 18.

Resolve debugger programs and Pylance Python-file configuration against the exact file resource without publishing false workspace interpreter changes. Preserve workspace fallback and activate differing program environments.

Part of microsoft/vscode-python-environments#1602.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@StellaHuang95 Stella Huang (StellaHuang95) added the feature-request Request for new features or functionality label Aug 28, 2026
@bschnurr

Bill Schnurr (bschnurr) commented Aug 28, 2026

Copy link
Copy Markdown
Member

🔒 Automated review in progress — Bill Schnurr (@bschnurr) is auto-reviewing this PR.

@bschnurr

Copy link
Copy Markdown
Member

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

Result: could-not-verify

Summary: Verification could not execute because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. The PR adds eight focused unit tests covering exact interpreter lookup, debugger selection, activation, and Pylance configuration. None could be run, so no pass/fail confidence is available. Two newly introduced debugger resolution paths appear uncovered.

Test runs: 1 not run

  • ⚠️ Not run | Verification preflight and test discovery | git status --short && git branch --show-current && git diff --name-status upstream/main...HEAD && node -e "const p=require('./package.json'); console.log(JSON.stringify({test:p.scripts.test,compile:p.scripts.compile,unit:p.scripts['test:unit'],pretest:p.scripts.pretest},null,2))" && if [ -d node_modules ]; then echo NODE_MODULES_PRESENT; else echo NODE_MODULES_MISSING; fi
⚠️ Verification preflight and test discovery diagnostic output
Container verification could not start: no trusted sandbox image is configured for microsoft/vscode-python. Local execution was not authorized for this PR HEAD.

@bschnurr Bill Schnurr (bschnurr) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@bschnurr Bill Schnurr (bschnurr) added the review-auto:approved Automated review: no blocking findings (approval posted). label Aug 28, 2026
@bschnurr

Copy link
Copy Markdown
Member

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

Result: could-not-verify

Summary: Verification was blocked because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. No tests meaningfully ran. The PR adds eight focused unit tests, but `${file}` debugger program resolution lacks explicit coverage.

Test runs: 1 not run

  • ⚠️ Not run | Verification preflight and test discovery | git status --short && git diff --name-status upstream/main...HEAD && git diff --unified=0 upstream/main...HEAD -- package.json .github\instructions\testing-workflow.instructions.md && if exist node_modules (echo NODE_MODULES_PRESENT) else (echo NODE_MODULES_MISSING) && if defined AUTOMATION_SANDBOX_PROFILE (echo AUTOMATION_SANDBOX_PROFILE=%AUTOMATION_SANDBOX_PROFILE%) else (echo AUTOMATION_SANDBOX_PROFILE_UNSET) && node -e "const p=require('./package.json'); console.log(JSON.stringify(p.scripts,null,2))"
⚠️ Verification preflight and test discovery diagnostic output
Container verification could not start: no trusted sandbox image is configured for microsoft/vscode-python, and local execution was not authorized.

@bschnurr

Copy link
Copy Markdown
Member

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

Result: could-not-verify

Summary: Verification could not run because no trusted sandbox image is configured for `microsoft/vscode-python`, and local execution was not authorized. The PR adds nine focused unit tests covering exact interpreter lookup, debugger selection/fallback, named workspaces, and activation. One documented path, `${file}` debugger program resolution, has no test coverage I could find.

Test runs: 1 not run

  • ⚠️ Not run | Dependency and test discovery | git status --short && git branch --show-current && git --no-pager diff --name-status upstream/main...HEAD && if [ -d node_modules ]; then echo NODE_MODULES_PRESENT; else echo NODE_MODULES_MISSING; fi && node -e "const p=require('./package.json'); console.log(JSON.stringify(p.scripts,null,2))"
⚠️ Dependency and test discovery diagnostic output
Container verification could not start: no trusted sandbox image is configured for microsoft/vscode-python. Local execution was not authorized for this PR HEAD.

Clarify why the workspace/configuration handler resolves the interpreter for the exact .py file (exactResource) rather than its folder: it honors a per-file environment such as a PEP 723 inline-script env for the language client this extension hosts, and only diverges from folder resolution when the Python Environments extension is in use.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The debug resolver now resolves \/\ to a program
URI and looks up the interpreter for that file. To avoid changing behavior
for users who are not using the environments extension (e.g. multi-root
debugging of a file that lives in a different folder than the launch
config), only take the program-scoped path when useEnvExtension() is true;
otherwise fall back to the historical workspace-folder resolution.
Environments-extension users still get per-file (inline-script) resolution.

Stub useEnvExtension() in the existing program-scoped resolver tests and
add a test covering the gated fallback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bschnurr

Copy link
Copy Markdown
Member

Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified.

Result: could-not-verify

Summary: No tests could run because the verification container lacked a trusted `microsoft/vscode-python` sandbox image, and local execution was not authorized. The PR adds nine focused unit-test cases covering exact interpreter lookup, debugger resolution, activation, and Pylance configuration. `${file}`-based debugger interpreter selection remains without direct coverage.

Test runs: 1 not run

  • ⚠️ Not run | Dependency and test discovery preflight | git status --short && git diff --name-status HEAD^...HEAD && printf '\n--- package scripts ---\n' && node -e "const p=require('./package.json'); console.log(JSON.stringify({scripts:p.scripts,engines:p.engines},null,2))" && printf '\n--- dependency markers ---\n' && test -d node_modules && echo 'node_modules=present' || echo 'node_modules=missing'
⚠️ Dependency and test discovery preflight diagnostic output
Container verification could not start and local execution was not authorized: no trusted sandbox image is configured for microsoft/vscode-python.

@StellaHuang95
Stella Huang (StellaHuang95) merged commit 7c12038 into microsoft:main Aug 31, 2026
49 checks passed
@StellaHuang95
Stella Huang (StellaHuang95) deleted the copilot/pep723-pr19 branch August 31, 2026 21:29

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

Stella Huang (StellaHuang95) added a commit to microsoft/vscode-python-environments that referenced this pull request Aug 31, 2026
> Part of #1602 (PEP 723 inline script env support). Design doc: #1601.

### Roadmap context

This is **PR 10 of 16** in the PEP 723 inline-script roadmap. PRs 7-9
persist, discover, validate, and route per-script environments; this PR
gives each configured script an exact project identity that survives
restart and can be consumed by per-file integrations.

| Phase 3 / integration | PR | Status |
|---|---|---|
| | PR 7: per-script persistence | merged (#1697) |
| | PR 8: activation-time discovery | merged (#1722) |
| | PR 9: automatic per-script routing | merged (#1729) |
| | **PR 10: exact script project registration** | **this PR** |
| | PRs 11-12: CodeLens and bulk setup UX | follow-up |
| | PR 17: Pylance per-file Python path |
[microsoft/pyrx#9265](microsoft/pyrx#9265) |
| | PR 19: Python extension per-file lookup |
[microsoft/vscode-python#26129](microsoft/vscode-python#26129)
|

### Why this PR

PR 9 can route a saved script to a validated inline environment, but the
project manager still identifies the script through its containing
workspace project. That prevents the per-file identity from surviving
restart consistently and leaves downstream configuration and
environment-change consumers without an exact script scope.

The registration also needs an ownership boundary: clearing inline
environments must remove entries created by the extension without
deleting user-authored project settings.

### What this PR does

- Registers an exact `pythonProjects` entry before binding an inline
environment.
- Stores the normal environment and package manager as the script's
fallback rather than replacing them with the inline manager.
- Marks extension-managed entries as either:
  - `created`: remove the entry during inline cleanup;
  - `adopted`: remove only the marker and preserve the user's entry.
- Ignores the managed fallback entry while a validated inline
association is routeable.
- Uses that entry normally when the feature is disabled or the
association becomes stale.
- Rolls back a newly prepared registration if inline association binding
fails.
- Supports single and batch script selection.
- Converts a managed entry into an ordinary user-owned entry when the
user explicitly selects a non-inline manager.
- Coordinates explicit managed selections with the dedicated clear-cache
operation.
- Resolves and updates same-named scripts correctly in multi-root
workspaces.

### Registration and cleanup semantics

| Condition | Behavior |
|---|---|
| No exact project entry exists | Create a marked entry containing the
ordinary fallback managers |
| An exact user entry exists | Temporarily mark it without changing its
manager choices |
| Inline binding fails | Roll back the marker/created entry and any
newly added in-memory project |
| Validated association exists | Route through the inline manager |
| Association is stale or unavailable | Use the stored fallback manager
|
| User selects a non-inline manager | Update the owning setting and
remove the managed marker |
| Clear Script Environment Cache | Remove created entries; restore
adopted entries |
| Two roots contain the same relative script path | Match using the
`workspace` discriminator |

### Performance and safety

- No workspace scan is introduced.
- Configuration writes occur only during explicit persisted selection,
rollback, manager changes, or dedicated cache cleanup.
- Serialization is limited to managed inline-script project mutations;
unrelated environment routing and refresh operations are not queued.
- User-owned project settings are never deleted by inline cleanup.

### User impact

The feature remains behind `python-envs.inlineScripts.enabled`. Existing
users and projects without a managed inline-script entry retain their
current manager-selection behavior. After setup, a script has a stable
exact project scope across reloads; when inline routing is unavailable,
its previous project/workspace environment remains the fallback.

### Tests

- `npm run compile-tests`
- `npm run compile`
- `npm run lint`
- Focused command, environment-manager, and settings tests: **92
passing**
- The full unit suite was also run. Six unchanged timing-sensitive
inline-manager tests failed only under full-suite load and passed
together in an isolated rerun.

### Scope and follow-up

This PR does not add the setup CodeLens, bulk setup command, TTL
eviction, or public feature enablement. Those remain in PRs 11, 12, and
14. Per-file language-service and debugger integration are handled by
the companion cross-repository PRs above.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature-request Request for new features or functionality review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants