Skip to content

Repository files navigation

WheelWhy

CI License: MIT Python 3.9–3.14

Stop guessing why a wheel is absent, whether it matches, or whether its archive is consistent. WheelWhy explains index candidates and local wheel Python, ABI, and platform components as correlated evidence around the authoritative complete tag. It also validates internal metadata and can stream-verify every RECORD hash and size without extracting, importing, or installing package code. Local-artifact commands make no network requests; the explicit availability command makes one bounded metadata request.

WheelWhy terminal demonstration

The screenshot uses synthetic demo wheels created by scripts/make_demo_wheels.py.

The problem

pip can reject a local artifact with one sentence:

ERROR: ... is not a supported wheel on this platform.

That sentence hides distinct fixes: use the right Python, download the right CPU build, choose a compatible operating-system wheel, avoid a debug ABI, or repair contradictory wheel metadata. pip issue #10793 has requested cause-specific errors since January 2022 and remains open. Its examples distinguish several causes, while a pip maintainer clarification correctly notes that the three tag components interact and cannot be treated as independent compatibility verdicts. The same generic failure continues to recur in recent user reports.

WheelWhy turns the declared tag/metadata decision into inspectable evidence and adds a separate, installer-independent archive integrity result:

$ wheelwhy explain tensor_demo-1.0-cp313-cp313-win_amd64.whl \
    --python 3.12 --platform win_amd64
WheelWhy  BLOCKED
tensor_demo-1.0-cp313-cp313-win_amd64.whl
Declared tags/metadata: NO MATCH for CPython 3.12 on win_amd64

  x Python   cp313
    no supported complete tag for CPython 3.12 on win_amd64 uses wheel interpreter value cp313
  x ABI      cp313
    no supported complete tag for CPython 3.12 on win_amd64 uses wheel abi value cp313
  ~ Platform win_amd64
    wheel platform value win_amd64 appears only in other supported complete tags

Next
  1. No supported complete target tag uses this Python component; obtain a wheel for cp312.
  2. No supported complete target tag uses this ABI component; rebuild for a valid target ABI.
  3. Obtain or build a wheel with a supported complete tag such as
     cp312-cp312-win_amd64, py3-none-any, or cp312-abi3-win_amd64.

Install

WheelWhy has one runtime dependency: PyPA's packaging, the same maintained tag implementation used throughout Python packaging.

Install the verified wheel from the latest GitHub release:

python -m pip install https://github.com/CAOShurong/wheelwhy/releases/download/v0.3.1/wheelwhy-0.3.1-py3-none-any.whl

Or download the self-contained zipapp and run it without installing:

python wheelwhy.pyz doctor
python wheelwhy.pyz explain path\to\package.whl

Release assets include SHA256SUMS and GitHub build-provenance attestations. WheelWhy is not yet published on PyPI; pip install wheelwhy is therefore not a documented install path.

Use

Explain why an index has no wheel for a target

wheelwhy availability "numpy==2.3.0" --python 3.9 --platform win_amd64

availability makes exactly one anonymous HTTPS request to the selected project's PEP 691 Simple API endpoint. It does not download a wheel, source archive, metadata sidecar, interpreter, or dependency. For the selected release it reports stable reasons such as file.requires_python, file.yanked, file.sdist, wheel.python_mismatch, wheel.abi_mismatch, and wheel.platform_mismatch, plus the newest compatible wheel release inside the same specifier when one exists.

WheelWhy explains a real PyPI release with no wheel for the target

The screenshot is an observed PyPI response captured on 2026-08-11. Index contents can change; JSON fixtures in the test suite make the reason and ordering contract reproducible.

Only one project requirement is accepted. Extras, markers, direct URLs, credentials, query strings, insecure HTTP, authenticated indexes, multiple indexes, and dependency resolution are deliberately out of scope. --index-url can select one anonymous PEP 691-compatible HTTPS index, and --timeout is bounded to 60 seconds.

Explain a real wheel on this machine

wheelwhy package-1.0-cp312-cp312-win_amd64.whl

The explain subcommand is optional when the first argument is a wheel path.

Verify the wheel before installation

wheelwhy verify package-1.0-py3-none-any.whl
wheelwhy verify package-1.0-py3-none-any.whl --json

verify first performs the same declared target check, then streams the archive and validates its required WHEEL, METADATA, and RECORD contract. It reports all deterministic findings with stable codes such as record.hash_mismatch, record.phantom_path, archive.unsafe_path, and wheel.tag_mismatch. It does not extract files.

WheelWhy verifies a synthetic wheel's compatibility and integrity

Explain only a filename

wheelwhy explain package-1.0-cp312-cp312-win_amd64.whl --filename

--filename is explicit because a misspelled path should not silently become a successful filename-only check. Archive metadata is marked uninspected in this mode.

Test a different target

wheelwhy explain package.whl --python 3.12 --platform manylinux_2_17_x86_64
wheelwhy explain package.whl --python 3.10 --implementation pp \
  --abi pypy310_pp73 --platform manylinux_2_17_x86_64

Synthetic targets are driven by explicit wheel platform tags. WheelWhy does not pretend to detect a remote machine's libc, macOS deployment target, or CPU.

Choose from a release directory

wheelwhy choose dist/
wheelwhy choose dist/ --python 3.12 --platform win_amd64 --json

The selected wheel is the compatible artifact with the highest preference in packaging.tags. Rejected candidates retain per-axis reasons.

WheelWhy selecting the best release artifact

Inspect the current target

wheelwhy tags --limit 30
wheelwhy doctor --json

Exit codes and automation

Code Meaning
0 A compatible wheel exists; for verify, structure and integrity also pass.
1 Deterministic unavailability/404, declared incompatibility, metadata/format defect, integrity failure, or no matching candidate.
2 Invalid arguments/path, network/auth/protocol failure, unreadable archive, or a resource-limit refusal.

--json emits schema_version: 1 and sends expected incompatibility results to stdout. Human input errors go to stderr; JSON input errors remain JSON on stdout for reliable automation.

Each JSON axis has a status: match means the component participates in a complete matching wheel tag, contextual means it occurs only in other supported complete tags named in related_target_tags, and mismatch means no supported complete tag uses that component. The existing axis compatible boolean is true only for match, so component membership is never presented as unconditional compatibility.

Integrity findings contain stable code, kind, severity, member, and message fields. Adding a new issue code is additive; scripts should use integrity.valid or the process exit before depending on individual codes.

What is checked

  • The complete wheel tag triple, in installer preference order, as the authoritative verdict.
  • Python/interpreter, ABI, and platform component evidence correlated back to complete target tags.
  • Requires-Python against the selected target.
  • Filename project/version against .dist-info/METADATA.
  • Filename tags against .dist-info/WHEEL Tag fields.
  • Required singleton Wheel-Version and Root-Is-Purelib declarations plus at least one Tag.
  • Core Metadata fields through PyPA packaging.metadata, including malformed or repeated fields.
  • With verify: strict three-column RECORD, safe relative paths, archive/manifest coverage, duplicate members and rows, fixed-output SHA-256-or-stronger hashes, and recorded sizes.
  • Resource limits: 1 MiB per metadata member, 16 MiB for RECORD, 100,000 archive members, and 8 GiB declared uncompressed content. Wheel contents are streamed, never extracted or run.
  • With availability: one PEP 691 JSON response capped at 8 MiB, project/release selection, Requires-Python, yanked state, source-versus-wheel type, and complete target tag triples.

Boundaries

WheelWhy explains declared tag and inspected-metadata compatibility using PyPA packaging. It does not call pip or uv's complete resolver and does not claim exact behavioral equivalence with either installer. availability uses its own documented deterministic release policy: prefer the newest non-yanked stable release satisfying the specifier, fall back to a matching pre-release when no stable one exists, and show an exactly pinned all-yanked release as unavailable evidence. That is an explanation policy, not pip/uv candidate selection. Dependency resolution, environment markers, multiple-index precedence, build policy, wheel variants, installer version, and other installer checks can still change the outcome. A listed source distribution is not proof that it builds.

WheelWhy also does not prove that native code is safe or functional, inspect shared-library dependencies, repair/build/install wheels, or replace testing on real target systems. A malicious or incorrectly compiled binary can carry plausible tags and valid hashes; RECORD proves self-consistency, not publisher identity or provenance. Use auditwheel, delocate, delvewheel, abi3audit, or platform testing for those jobs.

The design decision and alternative comparison are documented in docs/RESEARCH.md. Security reporting is in SECURITY.md.

Develop

python -m venv .venv
.\.venv\Scripts\python -m pip install -e ".[dev]"
.\.venv\Scripts\python -m pytest --cov=wheelwhy --cov-report=term-missing
.\.venv\Scripts\python -m ruff check .
.\.venv\Scripts\python -m mypy
.\.venv\Scripts\python -m build

See CONTRIBUTING.md for the compatibility-fixture rules and contribution flow.

License

WheelWhy is available under the MIT License. Release wheels include WheelWhy's license and the selected upstream BSD notice for PyPA packaging; the self-contained zipapp includes both licenses alongside the bundled code.

About

Explain exactly why a Python wheel matches or misses a target interpreter.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages