pypa/pip · error · UnsupportedWheel

{name} has an invalid wheel, {e}

Error message

{name} has an invalid wheel, {e}

What it means

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.

Source

Thrown at src/pip/_internal/utils/wheel.py:29

VERSION_COMPATIBLE = (1, 0)


logger = logging.getLogger(__name__)


def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]:
    """Extract information from the provided wheel, ensuring it meets basic
    standards.

    Returns the name of the .dist-info directory and the parsed WHEEL metadata.
    """
    try:
        info_dir = wheel_dist_info_dir(wheel_zip, name)
        metadata = wheel_metadata(wheel_zip, info_dir)
        version = wheel_version(metadata)
    except UnsupportedWheel as e:
        raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")

    check_compatibility(version, name)

    return info_dir, metadata


def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
    """Returns the name of the contained .dist-info directory.

    Raises AssertionError or UnsupportedWheel if not found, >1 found, or
    it doesn't match the provided name.
    """
    # Zip file path separators must be /
    subdirs = {p.split("/", 1)[0] for p in source.namelist()}

    info_dirs = [s for s in subdirs if s.endswith(".dist-info")]

    if not info_dirs:

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Re-download and verify the wheel hash against the index's recorded hash.
  2. Inspect with `unzip -l pkg.whl` and confirm a `.dist-info/` directory and a `WHEEL` file exist.
  3. If self-built, rebuild with a known-good backend (e.g. `python -m build` + `twine check`).
  4. Upgrade pip; very old wheels may use layouts modern pip rejects.

Example fix

// before
pip install ./dist/pkg-1.0.whl   # corrupt / missing .dist-info

// after
rm -rf dist && python -m build --wheel && twine check dist/* && pip install dist/pkg-1.0-*.whl
Defensive patterns

Strategy: validation

Validate before calling

import zipfile, sys
def wheel_looks_valid(path: str) -> bool:
    if not path.endswith('.whl'):
        return False
    if not zipfile.is_zipfile(path):
        return False
    with zipfile.ZipFile(path) as z:
        names = z.namelist()
        info_dirs = {n.split('/',1)[0] for n in names if n.endswith('.dist-info/')}
        if len(info_dirs) != 1:
            return False
        wheel_files = [n for n in names if n.endswith('/WHEEL')]
        if not wheel_files:
            return False
    return True

Type guard

null

Try / catch

from pip._internal.exceptions import UnsupportedWheel
try:
    parse_wheel(zipfile.ZipFile(path), name)
except UnsupportedWheel as e:
    log.warning('rejecting wheel: %s', e)
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/28e3b487ccf5c36b.json. Report an issue: GitHub.