Skip to content

fix(vendor/pypi_uv): reconstruct [package.metadata] for path-sourced uv packages - #167

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/uv-lock-preserve-package-metadata
Open

fix(vendor/pypi_uv): reconstruct [package.metadata] for path-sourced uv packages#167
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/uv-lock-preserve-package-metadata

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Problem

In vendored mode, the uv.lock rewriter (crates/socket-patch-core/src/vendor/pypi_uv.rs) converts the target [[package]] to a path source but omitted the [package.metadata] block (requires-dist + provides-extras) that a path source requires. Registry packages legitimately omit that block (uv fetches metadata from the index), but a path source has no index behind it, so uv records it inline.

Confirmed against real prod (uv 0.11.19, urllib3==1.26.18): right after scan --json --yes --mode vendored, uv lock --check exits 1 ("lockfile needs to be updated") and uv sync --locked fails identically. A plain uv sync then silently REWRITES uv.lock to re-add the dropped block (requires-dist for brotli/certifi/cryptography/… extras + provides-extras = ["brotli","secure","socks"]) — lock churn that also defeats a byte-exact vendor --revert.

The module doc claims the surgical edit keeps uv lock --check green — this restores that invariant.

Fix

Reconstruct the block from the vendored wheel's own core METADATA and append it inside the target unit rewrite, so the whole edit stays in the single uv_lock_package wiring fragment (revert byte-restores as one unit).

  • wire_uv (pypi_uv.rs:491-508): read the wheel at root.join(rel_wheel) and build the block before the lock surgery.
  • wheel_metadata_block / wheel_metadata_text (pypi_uv.rs:1035-1090): best-effort read of *.dist-info/METADATA from the wheel zip (size-capped).
  • parse_core_metadata_fields (pypi_uv.rs:1097): collect Requires-Dist / Provides-Extra, header block only (a Requires-Dist: line quoted in the long-description body can't leak).
  • parse_requires_dist (pypi_uv.rs:1124): PEP 508 → name (PEP 503-canonical) / extras / specifier (parens stripped, whitespace-compacted) / verbatim marker.
  • render_package_metadata_block / render_requires_dist_entry (pypi_uv.rs:1173-1233): emit uv's inline-table shape; double-quoted markers TOML-escaped.
  • rewrite_target_package_unit (pypi_uv.rs:775-844): append the block after the [[package]] body (]\n\n[package.metadata]…).

Emitted only when the wheel declares requirements/extras, so packages like six (none) still match the existing byte-identical fixtures. uv compares metadata semantically, so the raw METADATA markers/order are accepted without re-serialization — verified: uv lock --check and uv sync --locked pass and a plain uv sync produces zero churn for the exact bytes this code emits (urllib3==1.26.18).

Test

Extends the pypi_uv unit tests (all hermetic, no network):

  • direct_wiring_reconstructs_package_metadata_block_and_reverts — builds a minimal wheel (zip + METADATA) with an extra-gated dep, a legacy parenthesized base dep, and a poisoned description body; runs wire_uv; asserts the path-sourced unit gains the reconstructed [package.metadata] block, the body line never leaks, and vendor --revert byte-restores the original registry lock. Verified FAILS before the fix (no block appended), PASSES after.
  • parse_requires_dist_pulls_apart_name_extras_specifier_marker, core_metadata_fields_stop_at_the_header_block, render_block_reconstructs_requires_dist_and_provides_extras, render_block_escapes_double_quoted_markers_for_toml — pure-function coverage.

cargo test -p socket-patch-core --lib vendor::pypi_uv → 18 passed. Broader vendor::pypi (109) and vendor::toml_surgery (7) still green; clippy and rustfmt clean.

Scope

Single file (crates/socket-patch-core/src/vendor/pypi_uv.rs). No signature changes visible outside the module (wire_uv reads the wheel it is already handed via rel_wheel). Kills sweep findings uv-lock-rewrite-omits-package-metadata (P1) and its revert-byte-restore consequence.

🤖 Generated with Claude Code


Note

Medium Risk
Changes lock-file surgery for uv vendoring; incorrect metadata could break uv lock --check or revert fidelity, but scope is one module with strong tests and fail-closed parsing when METADATA is malformed.

Overview
When vendoring rewrites a registry [[package]] in uv.lock to a path wheel source, it now reconstructs and appends the [package.metadata] block (requires-dist, provides-extras) that uv expects for path sources but omits for registry entries.

wire_uv reads the vendored wheel’s *.dist-info/METADATA (size-capped), parses PEP 508 Requires-Dist / Provides-Extra from the RFC822 header only, and renders uv-shaped inline tables; rewrite_target_package_unit folds that block into the same uv_lock_package wiring fragment so vendor --revert still byte-restores the lock.

Packages with no deps/extras (e.g. six) still emit no block, preserving existing byte-identical fixtures. New unit tests cover parsing, TOML escaping, and a widget wheel scenario with revert.

Reviewed by Cursor Bugbot for commit 97f972a. Configure here.

…uv packages

Vendored-mode uv.lock rewriting converts the target package to a `path`
source but dropped the `[package.metadata]` block (requires-dist +
provides-extras) that a path source needs. Registry packages omit that
block (uv fetches metadata from the index), but a path source has no
index behind it, so uv records it inline. Without it, right after
`scan --mode vendored`, `uv lock --check` and `uv sync --locked` fail
("lockfile needs to be updated") and a plain `uv sync` silently rewrites
uv.lock to re-add the block — lock churn that also defeats a byte-exact
`vendor --revert`.

Reconstruct the block from the vendored wheel's own core METADATA
(Requires-Dist / Provides-Extra) at wire time and append it inside the
single `uv_lock_package` wiring fragment, so `uv lock --check` stays
green immediately and `vendor --revert` byte-restores the registry lock.
The block is emitted only when the wheel declares requirements/extras, so
packages like `six` (none) still match the byte-identical fixtures. uv
compares metadata semantically, so the raw METADATA markers/order are
accepted with zero churn (verified against uv 0.11.19, urllib3==1.26.18:
lock --check, sync --locked, and a no-op plain sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant