Honor project requires-python declarations for venv base interpreter - #446
Open
isaacbrodsky wants to merge 2 commits into
Open
Honor project requires-python declarations for venv base interpreter#446isaacbrodsky wants to merge 2 commits into
requires-python declarations for venv base interpreter#446isaacbrodsky wants to merge 2 commits into
Conversation
…D244) A project venv was always built on the pinned 3.12 (D214), and `requires-python` was read by nobody: a folder declaring `==3.11.*` was structurally unbuildable, and the failure was uv's raw requires-python conflict naming a 3.12 that appears in none of the user's files — no hint that the version came from the app, and no action that could fix it. The pin stays the default and becomes the starting point of a search. A declaration 3.12 satisfies changes nothing (same interpreter, same key, no download); one it fails moves the base interpreter to the nearest release the folder allows — `==3.11.*` → 3.11, `>=3.13` → 3.13, `<3.11` → 3.10 — acquired first under that version's own bootstrap key, then handed to `uv sync --python`. - projectenv reads the pin and evaluates it (fail-open on an unreadable declaration, fail-closed on an unreadable version). - envinstall resolves per version rather than per machine: one cache slot would let "ready for 3.12" stand in for "ready for the folder's 3.11". Candidate minors are derived from the specifier's own clauses, so there is no ceiling constant to go stale; the 3.10 floor is checked by a test that compiles our wrapper and upstream's child scripts at that grammar. - The app-interpreter fast path is declined for a folder whose pin this app fails — skipping the venv claims equivalence, and a version pin denies it. - A pin nothing can satisfy is refused before any install, with its own error type quoting the declaration instead of the generic "internal error (not your script)". - The worker's build-failure message now names the base interpreter — the one fact uv's verbatim text cannot supply. SPEC PY-19, D244; docs/usage.md and the authoring skill updated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017yR6kUqBNPWnF8292nFs3o
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a25b850. Configure here.
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
…on check Bugbot flagged patch-level pins being judged against bare `3.12`. Checked: `>=3.12.5`, `>3.12` and `==3.12.5` are NOT read as satisfied (they resolve to the 3.12 line), but `<=3.12.9` and `!=3.12.3` are — and either direction can land on a 3.12.x whose patch the folder forbade, because the resolution matches on (major, minor). In every one of those cases `uv sync` refuses in its own words, naming both versions, with the base interpreter beside it — loud and terminal, never a run on a Python the folder ruled out. The gap is that this module cannot promise a patch: what it can obtain is `uv python install 3.X`, whose patch is uv's choice. So the boundary is real and stays, but it is now written down where the reasoning lives (D177's corollary: a deliberate omission that is not recorded is indistinguishable from a bug) and pinned by a test covering both directions. What closing it properly would take — threading the specifier through the resolution and its cache, preferring a uv-managed newer patch, and a terminal answer for downloaded-and-still-unsatisfying — is recorded too, rather than left to be rediscovered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017yR6kUqBNPWnF8292nFs3o
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
This change implements support for honoring a project's
requires-pythondeclaration when building virtual environments. Previously, all project venvs were built on Python 3.12 regardless of what the project declared. Now, if a project specifies a different Python version requirement, the app will build the venv on the nearest compatible interpreter.Key Changes
Multi-version interpreter resolution: Changed from a single cached interpreter resolution to a per-version cache (
_python_by_versiondict) to support resolving different Python versions independently. This allows the app to be ready for 3.12 but not for a project's requested 3.11 simultaneously.New
project_python_version()function: Reads a project'srequires-pythondeclaration and determines the nearest compatible Python version to build on. ReturnsNoneif the project's requirement is already satisfied by the default 3.12, ensuring no unnecessary downloads.Version-specific bootstrap keys: Changed
PYTHON_BOOTSTRAP_KEYfrom a constant to a functionpython_bootstrap_key(version)that includes the version in the key. This prevents two different Python downloads from interfering with each other's progress tracking.Minimum Python floor: Added
_MIN_PROJECT_PYTHON_MINOR = 10(Python 3.10) as the oldest version the app will build on, with a test verifying the app's own code parses on this version.New
UnsupportedPythonRequirementexception: Raised when a project'srequires-pythoncannot be satisfied by any interpreter the app supports, with a clear error message quoting the declaration.Project environment integration: Added
requires_python_of(),python_pin(), andpython_pin_allows()functions toprojectenv.pyto read and evaluate project Python requirements using PEP 440 specifiers.Fast path gating: Added
_app_interpreter_may_run()check inengine.pyto prevent the fast path (skipping venv build) when a project pins a Python version that the app's interpreter doesn't satisfy.Worker updates: Modified
_env_install_worker.pyto accept the base interpreter as a parameter, allowing projects to specify their own Python version for venv building.Implementation Details
The resolution order for finding a suitable interpreter remains: explicit override → current interpreter (if matching version) → uv-managed interpreter → fallback to app's own.
Unreadable
requires-pythondeclarations are logged as warnings and ignored (fail-open), matching the behavior for invalid TOML and broken markers. This prevents typos from breaking all scripts in a folder.The version string is folded into bootstrap progress keys so that two projects requesting different Pythons don't interfere with each other's download tracking.
All existing behavior is preserved for projects that don't declare
requires-python— they continue to use the default 3.12 interpreter.https://claude.ai/code/session_017yR6kUqBNPWnF8292nFs3o
Note
Medium Risk
Changes core env install and
/api/runinterpreter selection; wrong pin logic could break venv builds or wrongly allow/deny the app fast path, though behavior for unpinned projects is explicitly preserved and heavily tested.Overview
Project
requires-pythonnow drives which Python builds each folder’s venv, instead of always using 3.12. Pins the default already satisfies behave as before; when they don’t, the app picks the nearest allowed minor (floor 3.10), downloads that interpreter under a version-specific bootstrap progress key, and runsuv sync --pythonwith it.projectenvreads and evaluates pins (PEP 440);envinstalladdsproject_python_version, per-version resolution cache,UnsupportedPythonRequirement, andproject_base_pythonfor the install worker.engineblocks the no-venv fast path when the app interpreter doesn’t satisfy the pin, fails unsatisfiable pins before install, and quotes the folder’s declaration in “needs Python” messages. Build failures now include the chosen base interpreter next to uv’s stderr.Docs/spec (PY-19, D244), usage.md, and authoring skill are updated; tests cover pin selection, bootstrap keys, worker argv, and fast-path gating.
Reviewed by Cursor Bugbot for commit 5e1e641. Bugbot is set up for automated code reviews on this repo. Configure here.