pypa/pip · error · UnsupportedWheel

has an invalid wheel

Error message

{name} has an invalid wheel, {e}

What it means

Raised as UnsupportedWheel from Distribution.from_wheel in pkg_resources.py:150-151. After the wheel zip is opened successfully, parsing the dist-info metadata (parse_wheel / read_wheel_metadata_file) may raise UnsupportedWheel for various malformations; pip re-raises with the package name prefixed so the offending wheel is identified.

Solutions

  1. Use a properly built wheel from another source/version.
  2. Rebuild the wheel with a standard backend (setuptools/hatch/poetry) that emits the required dist-info files.
  3. Inspect the wheel with `unzip -l file.whl` to confirm it contains a valid .dist-info directory.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before trusting a wheel, confirm it has a valid dist-info with METADATA/WHEEL:
import zipfile
with wheel.as_zipfile() as zf:
    names = zf.namelist()
    assert any(n.endswith('.dist-info/METADATA') for n in names), 'missing METADATA'
    assert any(n.endswith('.dist-info/WHEEL') for n in names), 'missing WHEEL'

Try / catch

from pip._internal.exceptions import UnsupportedWheel
try:
    dist = Distribution.from_wheel(wheel, name)
except UnsupportedWheel as e:
    if 'invalid wheel' in str(e):
        # use a properly built wheel
        ...

Prevention

When it happens

Trigger: Distribution.from_wheel in the pkg_resources backend: the zip opens fine but parse_wheel or read_wheel_metadata_file raises UnsupportedWheel (e.g. missing/invalid WHEEL or METADATA inside), caught and re-raised as UnsupportedWheel(f"{name} has an invalid wheel, {e}").

Common situations: A wheel missing the .dist-info directory or its METADATA/WHEEL files; a wheel with a malformed RECORD; a non-standard wheel built by a broken backend.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/a00d671b2a7fc2f8. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/metadata/pkg_resources.py:151

            metadata=InMemoryMetadata(metadata_dict, filename),
            project_name=project_name,
        )
        return cls(dist)

    @classmethod
    def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
        try:
            with wheel.as_zipfile() as zf:
                info_dir, _ = parse_wheel(zf, name)
                metadata_dict = {
                    path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
                    for path in zf.namelist()
                    if path.startswith(f"{info_dir}/")
                }
        except zipfile.BadZipFile as e:
            raise InvalidWheel(wheel.location, name) from e
        except UnsupportedWheel as e:
            raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
        dist = pkg_resources.DistInfoDistribution(
            location=wheel.location,
            metadata=InMemoryMetadata(metadata_dict, wheel.location),
            project_name=name,
        )
        return cls(dist)

    @property
    def location(self) -> str | None:
        return self._dist.location

    @property
    def installed_location(self) -> str | None:
        egg_link = egg_link_path_from_location(self.raw_name)
        if egg_link:
            location = egg_link
        elif self.location:
            location = self.location

View on GitHub (pinned to f399c37189)