{"record":{"id":"3ca6ba394326077f","repo":"nodejs/node","slug":"invalid-wheel-filename-wrong-number-of-parts-f","errorCode":null,"errorMessage":"Invalid wheel filename (wrong number of parts): {filename}","messagePattern":"Invalid wheel filename \\(wrong number of parts\\): (.+?)","errorType":"validation","errorClass":"InvalidWheelFilename","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/packaging/utils.py","lineNumber":114,"sourceCode":"    # Local version segment\n    if parsed.local is not None:\n        parts.append(f\"+{parsed.local}\")\n\n    return \"\".join(parts)\n\n\ndef parse_wheel_filename(\n    filename: str,\n) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:\n    if not filename.endswith(\".whl\"):\n        raise InvalidWheelFilename(\n            f\"Invalid wheel filename (extension must be '.whl'): {filename}\"\n        )\n\n    filename = filename[:-4]\n    dashes = filename.count(\"-\")\n    if dashes not in (4, 5):\n        raise InvalidWheelFilename(\n            f\"Invalid wheel filename (wrong number of parts): {filename}\"\n        )\n\n    parts = filename.split(\"-\", dashes - 2)\n    name_part = parts[0]\n    # See PEP 427 for the rules on escaping the project name.\n    if \"__\" in name_part or re.match(r\"^[\\w\\d._]*$\", name_part, re.UNICODE) is None:\n        raise InvalidWheelFilename(f\"Invalid project name: {filename}\")\n    name = canonicalize_name(name_part)\n\n    try:\n        version = Version(parts[1])\n    except InvalidVersion as e:\n        raise InvalidWheelFilename(\n            f\"Invalid wheel filename (invalid version): {filename}\"\n        ) from e\n\n    if dashes == 5:","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/packaging/utils.py#L96-L132","documentation":"InvalidWheelFilename raised when, after stripping '.whl', the stem does not contain exactly 4 or 5 dashes (the PEP 427 layout is name-version-(build)-python-abi-platform). 4 dashes = no build tag, 5 dashes = build tag present; anything else is structurally invalid.","triggerScenarios":"A filename like 'foo-1.0-py3-none-any-extra-junk.whl' (6 dashes) or 'foo-py3-none-any.whl' (3 dashes, missing version), or any wheel whose name/version/tag components themselves contain stray dashes.","commonSituations":"Hand-renamed wheel files, names with unescaped dashes that should have been replaced with underscores per PEP 427, or wheels produced by non-compliant build tools.","solutions":["Confirm the wheel was built by a standard PEP 517 backend (build, hatchling, setuptools) rather than renamed by hand.","Ensure project names with dashes are escaped to underscores in the wheel filename (the backend does this automatically).","Rebuild the wheel from source instead of editing the filename."],"exampleFix":"# before (manually renamed, name dash not escaped)\n# 'my-pkg-1.0-py3-none-any.whl'\n\n# after (backend rebuild emits escaped name)\n# 'my_pkg-1.0-py3-none-any.whl'","handlingStrategy":"validation","validationCode":"def has_wheel_dash_count(filename: str) -> bool:\n    if not filename.endswith('.whl'):\n        return False\n    return filename[:-4].count('-') in (4, 5)","typeGuard":null,"tryCatchPattern":"try:\n    name, ver, build, tags = parse_wheel_filename(fn)\nexcept InvalidWheelFilename as e:\n    if 'wrong number of parts' in str(e):\n        pass  # log and skip non-conformant wheel","preventionTips":["Build wheels only with PEP 517 backends.","Never hand-rename wheel files.","Escape dashes in project names to underscores in wheel filenames."],"tags":["packaging","utils","wheel","filename","pep427"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}