pypa/pip · error · InvalidWheelFilename

Invalid wheel filename (wrong number of parts): {filename!r}

Error message

Invalid wheel filename (wrong number of parts): {filename!r}

What it means

Raised by `parse_wheel_filename` when the stem (filename minus `.whl`) does not contain exactly 4 or 5 dashes. A well-formed wheel filename has 5 dash-separated parts (name, version, pytag, abitag, platformtag) or 6 when a build tag is present, so after stripping `.whl` the dash count must be 4 (no build) or 5 (with build). Any other count means the filename is structurally broken.

Source

Thrown at src/pip/_vendor/packaging/utils.py:210

    >>> ver == Version('1.0')
    True
    >>> tags == {Tag("py3", "none", "any")}
    True
    >>> not build
    True

    .. versionadded:: 26.1
       The *validate_order* parameter.
    """
    if not filename.endswith(".whl"):
        raise InvalidWheelFilename(
            f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
        )

    filename = filename[:-4]
    dashes = filename.count("-")
    if dashes not in (4, 5):
        raise InvalidWheelFilename(
            f"Invalid wheel filename (wrong number of parts): {filename!r}"
        )

    parts = filename.split("-", dashes - 2)
    name_part = parts[0]
    # See PEP 427 for the rules on escaping the project name.
    if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
        raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
    name = canonicalize_name(name_part)

    try:
        version = Version(parts[1])
    except InvalidVersion as e:
        raise InvalidWheelFilename(
            f"Invalid wheel filename (invalid version): {filename!r}"
        ) from e

    if dashes == 5:

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Rebuild the wheel with a compliant backend (setuptools/hatchling/flit) which escapes `-` in the name to `_`.
  2. Rename so name/version contain no unescaped dashes: replace `-` with `_` in the name and version segments, e.g. `my_pkg-1.0-py3-none-any.whl`.
  3. Confirm the expected 5-part (or 6-part with build) structure before parsing.
  4. Discard/re-download the artifact if externally produced and malformed.

Example fix

// before
parse_wheel_filename('my-pkg-1.0-py3-none-any.whl')  # 5 dashes -> raises

# after
parse_wheel_filename('my_pkg-1.0-py3-none-any.whl')  # 4 dashes -> ok
Defensive patterns

Strategy: try-catch

Validate before calling

def has_wheel_dash_count(filename: str) -> bool:
    if not filename.endswith('.whl'):
        return False
    return filename[:-4].count('-') in (4, 5)

Type guard

def is_well_formed_wheel(filename: object) -> bool:
    if not isinstance(filename, str) or not filename.endswith('.whl'):
        return False
    return filename[:-4].count('-') in (4, 5)

Try / catch

from pip._vendor.packaging.utils import parse_wheel_filename, InvalidWheelFilename

try:
    name, ver, build, tags = parse_wheel_filename(filename)
except InvalidWheelFilename as e:
    if 'wrong number of parts' in str(e):
        # rebuild or rename so the name has no unescaped dashes
        ...
    raise

Prevention

When it happens

Trigger: A wheel filename with a project name or version containing literal dashes that were not escaped to underscores during build, or a missing/extra tag component. Example: `my-pkg-1.0-py3-none-any.whl` (the unescaped dash in `my-pkg` inflates the dash count).

Common situations: Hand-renamed wheel files; project names built with hyphens that the build backend failed to normalize; truncation/corruption of the filename; mixing in non-wheel files that happen to end in `.whl`.

Related errors


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