pypa/pip · error · InvalidWheel

Wheel ' ' located at is invalid.

Error message

Wheel '{name}' located at {location} is invalid.

What it means

Raised as InvalidWheel from Distribution.from_wheel in _dists.py:141-142 when wheel.as_zipfile() raises zipfile.BadZipFile. The wheel file claims to be a zip but is structurally invalid (truncated, corrupt, or not actually a zip), so it cannot be opened; pip surfaces this as InvalidWheel with the location and name.

Solutions

  1. Clear pip's wheel/cache directory (pip cache purge) and re-download the wheel.
  2. Re-download from the index and verify the file size/hash matches the published values.
  3. If building locally, re-run the build to produce a complete wheel.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before opening, verify the wheel is a valid zip and complete:
import zipfile, os
if not zipfile.is_zipfile(wheel.location):
    raise SystemExit(f'{wheel.location} is not a valid zip; re-download')
if os.path.getsize(wheel.location) < expected_size:
    raise SystemExit('wheel appears truncated; re-download')

Try / catch

from pip._internal.exceptions import InvalidWheel
try:
    dist = Distribution.from_wheel(wheel, name)
except InvalidWheel:
    # purge cache and re-download a fresh wheel
    ...

Prevention

When it happens

Trigger: Distribution.from_wheel(wheel, name) opens the wheel; the underlying zipfile module detects corruption and raises BadZipFile, which is wrapped and re-raised as InvalidWheel(location, name) (str: "Wheel '{name}' located at {location} is invalid.").

Common situations: A download interrupted/corrupted by a network drop or proxy; a partial wheel left on disk after a failed install; a file mislabeled with a .whl extension that is not a zip; disk corruption.

Related errors


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

Appendix: source

Thrown at src/pip/_internal/metadata/importlib/_dists.py:142

        project_name: str,
    ) -> BaseDistribution:
        # Generate temp dir to contain the metadata file, and write the file contents.
        temp_dir = pathlib.Path(
            TempDirectory(kind="metadata", globally_managed=True).path
        )
        metadata_path = temp_dir / "METADATA"
        metadata_path.write_bytes(metadata_contents)
        # Construct dist pointing to the newly created directory.
        dist = importlib.metadata.Distribution.at(metadata_path.parent)
        return cls(dist, metadata_path.parent, None)

    @classmethod
    def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
        try:
            with wheel.as_zipfile() as zf:
                dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
        except zipfile.BadZipFile as e:
            raise InvalidWheel(wheel.location, name) from e
        return cls(dist, dist.info_location, pathlib.PurePosixPath(wheel.location))

    @property
    def location(self) -> str | None:
        if self._info_location is None:
            return None
        return str(self._info_location.parent)

    @property
    def info_location(self) -> str | None:
        if self._info_location is None:
            return None
        return str(self._info_location)

    @property
    def installed_location(self) -> str | None:
        if self._installed_location is None:
            return None

View on GitHub (pinned to f399c37189)