{"id":"7e1c4eed1ee79b01","repo":"pypa/pip","slug":"wheel-is-missing-wheel-version","errorCode":null,"errorMessage":"WHEEL is missing Wheel-Version","messagePattern":"WHEEL is missing Wheel-Version","errorType":"exception","errorClass":"UnsupportedWheel","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/utils/wheel.py","lineNumber":101,"sourceCode":"\n    try:\n        wheel_text = wheel_contents.decode()\n    except UnicodeDecodeError as e:\n        raise UnsupportedWheel(f\"error decoding {path!r}: {e!r}\")\n\n    # FeedParser (used by Parser) does not raise any exceptions. The returned\n    # message may have .defects populated, but for backwards-compatibility we\n    # currently ignore them.\n    return Parser().parsestr(wheel_text)\n\n\ndef wheel_version(wheel_data: Message) -> tuple[int, ...]:\n    \"\"\"Given WHEEL metadata, return the parsed Wheel-Version.\n    Otherwise, raise UnsupportedWheel.\n    \"\"\"\n    version_text = wheel_data[\"Wheel-Version\"]\n    if version_text is None:\n        raise UnsupportedWheel(\"WHEEL is missing Wheel-Version\")\n\n    version = version_text.strip()\n\n    try:\n        return tuple(map(int, version.split(\".\")))\n    except ValueError:\n        raise UnsupportedWheel(f\"invalid Wheel-Version: {version!r}\")\n\n\ndef check_compatibility(version: tuple[int, ...], name: str) -> None:\n    \"\"\"Raises errors or warns if called with an incompatible Wheel-Version.\n\n    pip should refuse to install a Wheel-Version that's a major series\n    ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when\n    installing a version only minor version ahead (e.g 1.2 > 1.1).\n\n    version: a 2-tuple representing a Wheel-Version (Major, Minor)\n    name: name of wheel or package to raise exception about","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/utils/wheel.py#L83-L119","documentation":"UnsupportedWheel raised by wheel_version when the parsed WHEEL metadata message has no Wheel-Version header at all. The Wheel-Version field is mandatory in wheel metadata (it declares the wheel spec major.minor). Its absence means the WHEEL file is incomplete or malformed.","triggerScenarios":"wheel_version accesses wheel_data['Wheel-Version']; if the Message has no such header it returns None and this error fires. Happens when a hand-written or third-party-tool-generated WHEEL file omits the field.","commonSituations":"Custom backend that doesn't emit Wheel-Version; tampered wheel where the header was stripped; an empty WHEEL file.","solutions":["Inspect: `unzip -p pkg.whl '*/WHEEL'` and confirm a `Wheel-Version:` line is present.","Rebuild with a standard PEP 517 backend which always emits Wheel-Version.","Re-download from a trusted index and verify the hash.","Run `twine check` to catch this before install."],"exampleFix":"// before\n# WHEEL file contained only 'Generator: mybuilder\\n'\npip install pkg-1.0-py3-none-any.whl\n\n// after\n# rebuild; backend emits:\n#   Wheel-Version: 1.0\nrm -rf dist && python -m build --wheel","handlingStrategy":"validation","validationCode":"import zipfile, email.parser\ndef wheel_has_version(path: str) -> bool:\n    with zipfile.ZipFile(path) as z:\n        entry = next((n for n in z.namelist() if n.endswith('/WHEEL')), None)\n        if not entry:\n            return False\n        msg = email.parser.Parser().parsestr(z.read(entry).decode('utf-8'))\n        return msg['Wheel-Version'] is not None","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Use a PEP 517 backend so WHEEL always has Wheel-Version.","Run `twine check` before publish/install.","Inspect WHEEL contents in CI.","Don't hand-craft WHEEL files."],"tags":["pip","wheel","metadata","validation"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}