nodejs/node · error · InvalidWheelFilename

Invalid wheel filename (extension must be '.whl'): {filename

Error message

Invalid wheel filename (extension must be '.whl'): {filename}

What it means

packaging.utils.InvalidWheelFilename raised by parse_wheel_filename when the filename does not end with '.whl'. This is the first sanity check before PEP 427 structural parsing; any other extension (or none) is rejected immediately.

Source

Thrown at tools/gyp/pylib/packaging/utils.py:107

    if parsed.post is not None:
        parts.append(f".post{parsed.post}")

    # Development release
    if parsed.dev is not None:
        parts.append(f".dev{parsed.dev}")

    # Local version segment
    if parsed.local is not None:
        parts.append(f"+{parsed.local}")

    return "".join(parts)


def parse_wheel_filename(
    filename: str,
) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:
    if not filename.endswith(".whl"):
        raise InvalidWheelFilename(
            f"Invalid wheel filename (extension must be '.whl'): {filename}"
        )

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

    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}")
    name = canonicalize_name(name_part)

    try:

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Branch on extension before parsing: use parse_sdist_filename for .tar.gz/.zip and parse_wheel_filename for .whl.
  2. Verify the filename ends with '.whl' (e.g. Path(filename).suffix == '.whl') before calling.
  3. Catch InvalidWheelFilename and skip or log non-wheel entries when iterating a list of mixed filenames.

Example fix

# before
name, ver, build, tags = parse_wheel_filename(fname)

# after
if fname.endswith('.whl'):
    name, ver, build, tags = parse_wheel_filename(fname)
elif fname.endswith(('.tar.gz', '.zip')):
    name, ver = parse_sdist_filename(fname)
else:
    raise ValueError(f'Unknown distribution type: {fname}')
Defensive patterns

Strategy: type-guard

Validate before calling

def is_wheel(filename: str) -> bool:
    return filename.endswith('.whl')

Type guard

from pathlib import Path
def is_wheel_path(p) -> bool:
    return Path(p).suffix == '.whl'

Try / catch

try:
    parsed = parse_wheel_filename(filename)
except InvalidWheelFilename:
    pass  # route to sdist parser or skip

Prevention

When it happens

Trigger: Calling parse_wheel_filename('foo-1.0.tar.gz'), parse_wheel_filename('foo-1.0-py3-none-any.zip'), or any string lacking the .whl suffix.

Common situations: Routing sdist and wheel filenames through the same parser, or passing a URL/path whose extension was stripped or altered.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/0428dc15126c64c3. Report an issue: GitHub.