{"record":{"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/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_internal/utils/wheel.py#L11-L47","documentation":"Raised by parse_wheel() as a catch-all wrapper: any UnsupportedWheel raised while locating the .dist-info directory, reading the WHEEL metadata, or parsing the Wheel-Version is re-raised prefixed with the wheel name. It signals that the wheel file is structurally invalid and cannot be installed.","triggerScenarios":"Calling parse_wheel(wheel_zip, name) where the zip fails one of the inner validation steps: wheel_dist_info_dir(), wheel_metadata(), or wheel_version(). The original UnsupportedWheel detail is appended after 'has an invalid wheel,'.","commonSituations":"A corrupt or hand-built wheel missing the .dist-info directory or WHEEL file. A wheel built by a very old or non-standard build tool. A truncated download producing an incomplete zip. A wheel whose metadata version pip cannot parse.","solutions":["Read the full error message — the text after 'has an invalid wheel,' identifies the specific sub-failure (dist-info, metadata, or version)","Rebuild the wheel with a current build tool: 'python -m build --wheel'","Re-download the wheel from the official index to rule out a truncated/corrupt download","Upgrade pip to the latest version in case the wheel uses a newer but compatible format","Inspect the wheel zip contents: 'unzip -l <wheel>' and verify a .dist-info/WHEEL file exists"],"exampleFix":"// before\npip install ./custom_built-1.0.whl\n// after\n# rebuild with a standard, up-to-date toolchain\npython -m pip install --upgrade build\npython -m build --wheel\npip install dist/custom_built-1.0-py3-none-any.whl","handlingStrategy":"try-catch","validationCode":"import zipfile\nfrom zipimport import ZipImportError\n\ndef is_valid_wheel(path: str) -> bool:\n    if not path.endswith('.whl'):\n        return False\n    try:\n        zf = zipfile.ZipFile(path)\n    except (zipfile.BadZipFile, FileNotFoundError):\n        return False\n    names = zf.namelist()\n    dirs = {n.split('/')[0] for n in names if '/' in n}\n    info_dirs = [d for d in dirs if d.endswith('.dist-info')]\n    if len(info_dirs) != 1:\n        return False\n    has_wheel = any(n.endswith('.dist-info/WHEEL') for n in names)\n    return has_wheel","typeGuard":"import zipfile\n\ndef is_installable_wheel(path: str) -> bool:\n    try:\n        with zipfile.ZipFile(path) as zf:\n            return any(n.split('/')[0].endswith('.dist-info') for n in zf.namelist() if '/' in n)\n    except (zipfile.BadZipFile, FileNotFoundError):\n        return False","tryCatchPattern":"from pip._internal.exceptions import UnsupportedWheel\n\ntry:\n    parse_wheel(wheel_zip, name)\nexcept UnsupportedWheel as e:\n    # the detail after 'has an invalid wheel,' tells you the specific issue\n    raise PackagingError(f'wheel {name} is malformed: {e}')","preventionTips":["Build wheels with standard backends (setuptools, hatchling, flit)","Verify wheels with 'unzip -l <file>.whl' before publishing or installing","Re-download wheels from the official index if downloads may be corrupt","Upgrade pip to handle newer wheel formats"],"tags":["wheel","metadata","installation","packaging"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}