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
- Use a single underscore '_' to separate words in the escaped project name, not '__'.
- Ensure the project name is non-empty and contains only [\w._] characters.
- 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
- Use single underscores '_' not '__' for hyphens in escaped project names
- Ensure the project name segment is non-empty
- Validate the name segment against [\w._]+ before parsing
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
- Invalid build number
- Invalid wheel filename (extension must be '.whl')
- Invalid wheel filename (wrong number of parts)
- Invalid sdist filename (empty project name)
- Invalid wheel filename (compressed tag set components must…
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)