pypa/pip · error · UnsupportedWheel

Error decoding metadata for

Error message

Error decoding metadata for {wheel}: {e} in {filename} file

What it means

Raised as UnsupportedWheel from WheelDistribution.read_text in _dists.py:88-93. When reading a metadata file out of an in-memory wheel, if the bytes fail to decode as UTF-8 (UnicodeDecodeError), pip wraps it with the wheel location and offending filename because non-UTF-8 metadata is not spec-compliant and cannot be parsed.

Solutions

  1. Obtain a replacement wheel from a different source/version that has UTF-8 metadata.
  2. Rebuild the wheel with a compliant build backend (setuptools/poetry/hatch) that writes UTF-8 METADATA.
  3. If you control the package, ensure all metadata files are UTF-8 encoded.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before reading wheel metadata, verify it decodes as UTF-8:
with wheel.as_zipfile() as zf:
    for p in zf.namelist():
        if p.endswith(('.dist-info/METADATA',)):
            zf.read(p).decode('utf-8')  # raises early if not UTF-8

Try / catch

from pip._internal.exceptions import UnsupportedWheel
try:
    dist = Distribution.from_wheel(wheel, name)
except UnsupportedWheel as e:
    if 'Error decoding metadata' in str(e):
        # fetch an alternative wheel or rebuild
        ...

Prevention

When it happens

Trigger: Distribution.from_wheel -> WheelDistribution.from_zipfile reads a file (e.g. METADATA/RECORD) from the wheel zip; the bytes raise UnicodeDecodeError on .decode('utf-8'); UnsupportedWheel is raised.

Common situations: A wheel whose METADATA was authored in a legacy/non-UTF-8 encoding (Latin-1, cp1252); a corrupted or truncated wheel where decode fails; a wheel produced by a broken build tool that wrote binary into a text metadata field.

Related errors


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

Appendix: source

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

        return cls(files, info_location)

    def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
        # Only allow iterating through the metadata directory.
        if pathlib.PurePosixPath(str(path)) in self._files:
            return iter(self._files)
        raise FileNotFoundError(path)

    def read_text(self, filename: str) -> str | None:
        try:
            data = self._files[pathlib.PurePosixPath(filename)]
        except KeyError:
            return None
        try:
            text = data.decode("utf-8")
        except UnicodeDecodeError as e:
            wheel = self.info_location.parent
            error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
            raise UnsupportedWheel(error)
        return text

    def locate_file(self, path: str | PathLike[str]) -> pathlib.Path:
        # This method doesn't make sense for our in-memory wheel, but the API
        # requires us to define it.
        raise NotImplementedError


class Distribution(BaseDistribution):
    def __init__(
        self,
        dist: importlib.metadata.Distribution,
        info_location: BasePath | None,
        installed_location: BasePath | None,
    ) -> None:
        self._dist = dist
        self._info_location = info_location
        self._installed_location = installed_location

View on GitHub (pinned to f399c37189)