{"id":"28e3b487ccf5c36b","repo":"pypa/pip","slug":"name-has-an-invalid-wheel-e-28e3b4","errorCode":null,"errorMessage":"{name} has an invalid wheel, {e}","messagePattern":"(.+?) has an invalid wheel, (.+?)","errorType":"exception","errorClass":"UnsupportedWheel","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/utils/wheel.py","lineNumber":29,"sourceCode":"\nVERSION_COMPATIBLE = (1, 0)\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:\n    \"\"\"Extract information from the provided wheel, ensuring it meets basic\n    standards.\n\n    Returns the name of the .dist-info directory and the parsed WHEEL metadata.\n    \"\"\"\n    try:\n        info_dir = wheel_dist_info_dir(wheel_zip, name)\n        metadata = wheel_metadata(wheel_zip, info_dir)\n        version = wheel_version(metadata)\n    except UnsupportedWheel as e:\n        raise UnsupportedWheel(f\"{name} has an invalid wheel, {e}\")\n\n    check_compatibility(version, name)\n\n    return info_dir, metadata\n\n\ndef wheel_dist_info_dir(source: ZipFile, name: str) -> str:\n    \"\"\"Returns the name of the contained .dist-info directory.\n\n    Raises AssertionError or UnsupportedWheel if not found, >1 found, or\n    it doesn't match the provided name.\n    \"\"\"\n    # Zip file path separators must be /\n    subdirs = {p.split(\"/\", 1)[0] for p in source.namelist()}\n\n    info_dirs = [s for s in subdirs if s.endswith(\".dist-info\")]\n\n    if not info_dirs:","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/utils/wheel.py#L11-L47","documentation":"UnsupportedWheel wrapper raised by parse_wheel when any of its sub-checks (dist-info dir lookup, metadata read, version parse) fails. The underlying error message is appended after 'X has an invalid wheel, ...'. This is the umbrella exception for malformed wheel internals before compatibility is even checked.","triggerScenarios":"Calling parse_wheel(wheel_zip, name) on a .whl that fails wheel_dist_info_dir, wheel_metadata, or wheel_version — for example a wheel with no .dist-info, a corrupt zip, missing WHEEL file, or unparseable Wheel-Version.","commonSituations":"Truncated/corrupted wheel download; hand-rolled wheel missing the .dist-info directory; wheel built by a broken backend; zip opened in text mode during build corrupting it.","solutions":["Re-download and verify the wheel hash against the index's recorded hash.","Inspect with `unzip -l pkg.whl` and confirm a `.dist-info/` directory and a `WHEEL` file exist.","If self-built, rebuild with a known-good backend (e.g. `python -m build` + `twine check`).","Upgrade pip; very old wheels may use layouts modern pip rejects."],"exampleFix":"// before\npip install ./dist/pkg-1.0.whl   # corrupt / missing .dist-info\n\n// after\nrm -rf dist && python -m build --wheel && twine check dist/* && pip install dist/pkg-1.0-*.whl","handlingStrategy":"validation","validationCode":"import zipfile, sys\ndef wheel_looks_valid(path: str) -> bool:\n    if not path.endswith('.whl'):\n        return False\n    if not zipfile.is_zipfile(path):\n        return False\n    with zipfile.ZipFile(path) as z:\n        names = z.namelist()\n        info_dirs = {n.split('/',1)[0] for n in names if n.endswith('.dist-info/')}\n        if len(info_dirs) != 1:\n            return False\n        wheel_files = [n for n in names if n.endswith('/WHEEL')]\n        if not wheel_files:\n            return False\n    return True","typeGuard":"null","tryCatchPattern":"from pip._internal.exceptions import UnsupportedWheel\ntry:\n    parse_wheel(zipfile.ZipFile(path), name)\nexcept UnsupportedWheel as e:\n    log.warning('rejecting wheel: %s', e)\n    raise","preventionTips":["Always run `twine check` on built wheels before publishing/installing.","Re-download and verify wheel hashes.","Use standard PEP 517 backends.","Validate with `unzip -l` in CI."],"tags":["pip","wheel","validation","metadata"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}