pypa/pip · error · InvalidWheelFilename

Invalid project name

Error message

Invalid project name: {filename!r}

What it means

Raised by parse_wheel_filename() when the project-name portion of the wheel stem fails validation (utils.py:270). The name must match _wheel_name_regex (^\w._+$) and must not contain '__' (double underscore is reserved by PEP 427 as the escape for '-'). An empty name also fails this check since the regex requires at least one character.

Solutions

  1. Use a single underscore '_' to separate words in the escaped project name, not '__'.
  2. Ensure the project name is non-empty and contains only [\w._] characters.
  3. Run the name through canonicalize_name() and re-escape dashes as single underscores.

Example fix

// before
parse_wheel_filename('foo__bar-1.0-py3-none-any.whl')
// after
parse_wheel_filename('foo_bar-1.0-py3-none-any.whl')
Defensive patterns

Strategy: validation

Validate before calling

import re

_wheel_name_regex = re.compile(r"^[\w._]+\Z", re.UNICODE)

def safe_parse_wheel(filename: str):
    stem = filename[:-4]
    parts = stem.split("-", 2)
    name_part = parts[0]
    if "__" in name_part or _wheel_name_regex.match(name_part) is None:
        raise ValueError(f"Invalid project name in wheel: {filename!r}")
    return parse_wheel_filename(filename)

Type guard

def has_valid_wheel_project_name(filename: str) -> bool:
    if not isinstance(filename, str) or not filename.endswith(".whl"):
        return False
    name_part = filename[:-4].split("-", 1)[0]
    return "__" not in name_part and bool(re.match(r"^[\w._]+$", name_part))

Try / catch

from packaging.utils import InvalidWheelFilename

try:
    parse_wheel_filename(filename)
except InvalidWheelFilename as e:
    if "Invalid project name" in str(e):
        logger.error("Wheel has invalid project name: %s", filename)
    raise

Prevention

When it happens

Trigger: A wheel filename whose name segment contains '__' (double underscore), is empty, or contains characters outside [\w._]. For example '-1.0-py3-none-any.whl' (empty name) or 'foo__bar-1.0-py3-none-any.whl' (double underscore).

Common situations: A package name with a dash that was incorrectly escaped as '__' instead of '_', or a filename that starts with a version because the name was omitted. Also seen when a malformed wheel is produced by a non-standard build tool.

Related errors


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

Appendix: source

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

       empty.
    """
    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 _wheel_name_regex.match(name_part) 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:
        build_part = parts[2]
        build_match = _build_tag_regex.match(build_part)
        if build_match is None:
            raise InvalidWheelFilename(
                f"Invalid build number: {build_part} in {filename!r}"
            )
        build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
    else:

View on GitHub (pinned to f399c37189)