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
- Re-download and verify the wheel hash against the index's recorded hash.
- Inspect with `unzip -l pkg.whl` and confirm a `.dist-info/` directory and a `WHEEL` file exist.
- If self-built, rebuild with a known-good backend (e.g. `python -m build` + `twine check`).
- 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
- Always run `twine check` on built wheels before publishing/installing.
- Re-download and verify wheel hashes.
- Use standard PEP 517 backends.
- Validate with `unzip -l` in CI.
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
- multiple .dist-info directories found: {}
- .dist-info directory {info_dir!r} does not start with {canon
- error decoding {path!r}: {e!r}
- WHEEL is missing Wheel-Version
- invalid Wheel-Version: {version!r}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/28e3b487ccf5c36b.json.
Report an issue: GitHub.