Repository navigation
Fix hosted uv pinning dynamic dependencies (#639) - #1709
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A uv project with dynamic = ["dependencies"] gets its requirement list from the build backend, so pyproject.toml has no static declaration. Hosted scan still repointed the lock root's requires-dist entry, and rollback, remove and the vendored takeover then always refused because they derive the restored specifier from that declaration. Hosted scan now refuses such a pin up front with redirect_uv_metadata_unsupported and writes nothing, as vendored mode does with pypi_uv_dynamic_dependencies. Packages a dynamic project only pulls in transitively are still wired through override-dependencies, which unwind cleanly. Fixes #639 Assisted-by: Claude Code:claude-opus-5-5
The seeded uv rewrite sweep generates locks whose root requires-dist names packages its pyproject.toml doesn't declare. Those cases are now refused the same way as a dynamic-dependencies project, so 25 of the 300 chunks change. Every changed chunk is one where a seed hits the new refusal, and no other chunk moves. Also document the hosted refusal next to vendored mode's pypi_uv_dynamic_dependencies in the uv compatibility notes. Refs #639 Assisted-by: Claude Code:claude-opus-5-5
Review found the first guard both too broad and too narrow. It refused any lock whose root requires a package the pyproject doesn't declare, including stale locks unrelated to #639, which moved 25 golden chunks. And a static dependency group naming the package let a dynamic project's backend-supplied requires-dist entry through, although restore matches that entry only against [project] declarations. Now the refusal fires exactly when project.dynamic lists dependencies or optional-dependencies and the lock root's requires-dist names the package. The uv rewrite golden is back to main's, and a test covers the dependency-group shape. Refs #639 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 85a9881. Configure here.
|
Labeled Ready for review by the burn-down agent.
Generated by Claude Code |
Final review briefWhat it does. A uv project with PEP 621 Risk: low. Hosted uv path only (the single caller of Look here
Verified. Read the full diff and the call graph (only hosted redirect reaches the new check; vendored keeps its own Changes I made. None. Open questions (non-blocking). With Auto-merge is armed, so approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #639
What changes for users
In a uv project with
dynamic = ["dependencies"](or"optional-dependencies"), the build backend supplies the dependencylist (for example setuptools reading
requirements.in).scan --mode hostedused to redirect such a dependency and exit 0. Afterthat, rollback, remove and the vendored takeover all refused with
"pyproject.toml no longer declares six", so the only way back was
git checkout.Hosted scan now refuses that package up front with
redirect_uv_metadata_unsupportedand writes nothing. Vendored modealready refuses this shape (
pypi_uv_dynamic_dependencies). A packagethat a dynamic project only pulls in transitively is still hosted-patched
through
override-dependencies, which rollback undoes cleanly.Root cause
finish_metadata_completion(utils/python_lock.rs) repoints everyroot-package
requires-distentry naming the patched package. Upstreamrestore (
patch/redirect/upstream/uv.rs,declared_clauses) rebuildsthat entry's
specifierfrom the pyproject's static[project]declaration. A dynamic project has no static declaration, so restore
can't undo the entry. The new
refuse_dynamic_root_requirementruns inprepare_metadata_completion, before anything is mutated. It refuseswhen
project.dynamiclists dependencies or optional-dependencies andthe lock root's
requires-distnames the package.requires-devcomesfrom PEP 735 groups, which can't be dynamic, so it isn't checked.
Tests
patch::redirect::uv_dynamic_dependencies::backend_declared_dependency_is_refused_before_writing(dynamic deps, dynamic optional extra, dynamic deps + static dependency group; LF and CRLF)"dependencies": wrote ["pyproject.toml", "uv.lock"]…::transitive_dependency_of_a_dynamic_project_is_still_wiredLocal runs:
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test -p socket-patch-core --all-features --no-fail-fast: 6727 passed in the lib. The 4 failures are chmod-based tests that can't fail when run as root in this container (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_…,pypi_requirements::wire_failure_rolls_back_…). They're unrelated to this diff, and CI runs as non-root. All core integration tests pass. The seededpython_lock_rewritegolden is unchanged from main.cargo test -p socket-patch-cli --all-features --lib --bins: 933 passedcargo test -p socket-patch-cli --all-features --test e2e_redirect_uv_build -- --ignored(uv 0.11.32): 9 passedcargo test --workspace --all-featuresto completion: linking every CLI e2e binary filled the container disk. CI covers it.npm/,pypi/,gem/) changes are needed: this is core planning logic only.Code review (
/code-review high)dynamic = ["dependencies"]project, but rollback, remove and the vendored takeover then always refuse ("pyproject.toml no longer declares six") #639 and moved 25 golden chunks. It also let the dynamic shape through when a static dependency group named the package. The guard is now limited to dynamic projects and checksrequires-distdirectly, and a test covers the group shape.dynamic = ["optional-dependencies"], and no longer claim a takeover works for dynamic projects. The refusal wording no longer says "unpinned".dynamic = ["dependencies"], not"optional-dependencies".is_root_packagematches vendored'slock_has_root_packagepredicate. It's shared here withfinish_metadata_completion, and unifying it with vendored mode would widen this PR.Follow-ups
git checkout -- pyproject.toml uv.lockto unwind. The pre-scan specifier was never recorded, so it can't be derived.https://claude.ai/code/session_01AqSzQQq8YyzdQ9f84KtWwQ
Generated by Claude Code