fix(pypi): resolve name and version for PEP 658 metadata sidecars - #222
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes PyPI filename parsing so PEP 658/714 core-metadata sidecar downloads (*.metadata) resolve to the correct package name/version instead of creating synthetic _hash_*@0 identifiers, improving cache key stability and package overview correctness.
Changes:
- Strip the
.metadatasuffix before parsing distribution filenames (PEP 658/714 sidecars). - Rework wheel parsing to use spec-guaranteed field positions (PEP 427), correctly handling build-tagged wheels.
- Expand recognized distribution extensions (more sdist archives + legacy bdists) and extend unit tests accordingly.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/handler/pypi.go | Updates parseFilename to handle .metadata sidecars and broaden filename parsing across more PyPI distribution formats. |
| internal/handler/pypi_test.go | Extends TestPyPIParseFilename and adds a regression test to prevent _hash_* fallback for sidecars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Legacy bdist formats | ||
| {"numpy-1.8.0-py2.7-macosx-10.9-x86_64.egg", "numpy", "1.8.0"}, | ||
| {"pywin32-223.win32-py2.7.exe", "pywin32", "223.win32"}, | ||
|
|
| for _, format := range taggedExtensions { | ||
| if !strings.HasSuffix(filename, format.ext) { | ||
| continue | ||
| } | ||
| parts := strings.Split(strings.TrimSuffix(filename, format.ext), "-") | ||
| if len(parts) < format.minParts { | ||
| return "", "" | ||
| } | ||
| return parts[0], parts[1] | ||
| } |
andrew
left a comment
There was a problem hiding this comment.
The new .exe handling parses Windows installer filenames incorrectly. For foo-1.0.win32-py2.0.exe, parseFilename returns version 1.0.win32, but win32 is the platform and the version is 1.0. The pywin32 test currently codifies the same error. Please parse Windows installers separately or remove .exe support, and add a regression test using the documented filename form.
The bdist_wininst and bdist_msi layout joins the platform to the version
with a '.' rather than a '-', so treating .exe/.msi like a wheel folded
the platform into the version: foo-1.0.win32-py2.0.exe resolved to
version "1.0.win32". Eggs shared the problem, as setuptools' hyphen
escaping is not universal: aws-sdk-1.0.0-py3.11.egg resolved to name
"aws", version "sdk".
Give each format its own parser. Wheels keep the PEP 427
spec-guaranteed field positions, eggs locate the version relative to the
py{X.Y} interpreter field, and Windows installers strip the platform and
interpreter fields before splitting name from version.
A PEP 658 sidecar resolves to the same name and version as the
distribution it describes, so it is cached under that version. Browse and
compare took the first cached artifact without checking its extension,
handing openArchive plain text: a version pip had only fetched metadata
for reported hasCached and then 500'd.
Add firstBrowsableArtifact, replacing five duplicated selection loops,
and export PyPIMetadataSuffix so the suffix has a single definition.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
according to claude all issues have been addressed... |
hi,
i was experiencing this while caching pypi dependencies
once again i left the fix to claude (opus), but please challenge me if you have any questions
Problem
PyPI packages sometimes appear in the package overview under a synthetic name like pkg:pypi/_hash_164a211cb654c0d4@0.
These are PEP 658/714 core-metadata sidecar files. When an index advertises data-core-metadata (simple API) or core-metadata (JSON API) for a file, pip fetches .metadata to resolve dependencies without downloading the full wheel. parseFilename only recognised .whl and four sdist extensions, so foo-1.2.0-py3-none-any.whl.metadata matched none of them and returned an empty name. handleDownload then fell back to a hash-derived identifier:
Reproduced against the reported entry — hashPath of the request path for backports_asyncio_runner-1.2.0-py3-none-any.whl.metadata yields exactly 164a211cb654c0d4.
It looks intermittent because the sidecar fetch depends on the index advertising core metadata per-file and pip choosing metadata-only resolution. For backports_asyncio_runner 1.2.0, upstream advertises core metadata on the wheel but not the sdist.
Changes
Testing
Note
This stops new hash* entries from being created but does not clean up existing ones. The cache is keyed on (package PURL, version PURL, filename), so already-cached sidecars keep their old identity and remain visible in the overview until purged.