{"record":{"id":"fe8d64683ccc2f35","repo":"nodejs/node","slug":"invalid-project-name-filename","errorCode":null,"errorMessage":"Invalid project name: {filename}","messagePattern":"Invalid project name: (.+?)","errorType":"validation","errorClass":"InvalidWheelFilename","httpStatus":null,"severity":"error","filePath":"tools/gyp/pylib/packaging/utils.py","lineNumber":122,"sourceCode":"    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:\n        build_part = parts[2]\n        build_match = _build_tag_regex.match(build_part)\n        if build_match is None:\n            raise InvalidWheelFilename(\n                f\"Invalid build number: {build_part} in '{filename}'\"\n            )\n        build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))\n    else:","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/gyp/pylib/packaging/utils.py#L104-L140","documentation":"InvalidWheelFilename raised when the name segment of the wheel filename is invalid per PEP 427 escaping rules: it contains '__' (the escape sequence for a literal dash) in an illegal position, or it fails the ^[\\w\\d._]*$ regex (e.g. contains spaces or other punctuation).","triggerScenarios":"A wheel filename whose first dash-separated part (before the version) contains '__' inappropriately or non-word characters. This check runs after the dash-count check passes.","commonSituations":"Project names with unusual characters that the build backend failed to escape correctly, or filenames edited/constructed manually violating the PEP 427 name-escaping convention.","solutions":["Rebuild the wheel with a conformant backend so the name is escaped to ^[\\w._]+$ with dashes encoded as '__'.","Rename the project to use only ASCII letters, digits, underscores, and dots if escaping is the issue.","Do not hand-edit wheel filenames; regenerate from sdist."],"exampleFix":"# before (space in name part)\n# 'my pkg-1.0-py3-none-any.whl'\n\n# after (rebuilt with normalized name)\n# 'my_pkg-1.0-py3-none-any.whl'","handlingStrategy":"validation","validationCode":"import re\ndef valid_wheel_name_part(filename: str) -> bool:\n    if not filename.endswith('.whl'):\n        return False\n    name_part = filename[:-4].split('-')[0]\n    return '__' not in name_part and bool(re.match(r'^[\\w\\d._]*$', name_part, re.UNICODE))","typeGuard":null,"tryCatchPattern":"try:\n    parse_wheel_filename(fn)\nexcept InvalidWheelFilename as e:\n    if 'project name' in str(e):\n        pass  # rebuild wheel with normalized name","preventionTips":["Keep project names to ASCII alphanumerics, ., _, -.","Let the backend perform PEP 427 name escaping.","Avoid manual filename construction."],"tags":["packaging","utils","wheel","filename","naming","pep427"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}