{"record":{"id":"b08b1f0258e7184b","repo":"pypa/pip","slug":"invalid-wheel-filename-extension-must-be-whl","errorCode":null,"errorMessage":"Invalid wheel filename (extension must be '.whl'): {filename!r}","messagePattern":"Invalid wheel filename \\(extension must be '\\.whl'\\): (.+?)","errorType":"validation","errorClass":"InvalidWheelFilename","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/packaging/utils.py","lineNumber":256,"sourceCode":"    True\n    >>> not build\n    True\n\n    .. versionadded:: 20.9\n\n    .. versionchanged:: 23.2\n       Raises :class:`InvalidWheelFilename` when the version component is invalid.\n\n    .. versionadded:: 26.1\n       The *validate_order* parameter.\n\n    .. versionchanged:: 26.3\n       Raises :class:`InvalidWheelFilename` when an interpreter component is\n       not an identifier, a tag set component is empty, or the project name is\n       empty.\n    \"\"\"\n    if not filename.endswith(\".whl\"):\n        raise InvalidWheelFilename(\n            f\"Invalid wheel filename (extension must be '.whl'): {filename!r}\"\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!r}\"\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 _wheel_name_regex.match(name_part) is None:\n        raise InvalidWheelFilename(f\"Invalid project name: {filename!r}\")\n    name = canonicalize_name(name_part)\n\n    try:","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/pypa/pip/blob/f399c3718970b1b0e2478dac5296eb62679a9b86/src/pip/_vendor/packaging/utils.py#L238-L274","documentation":"Raised by parse_wheel_filename() when the filename does not end with the '.whl' extension. The wheel binary distribution format (PEP 427) mandates the .whl suffix, and this is the very first structural check at utils.py:255. The exception class is InvalidWheelFilename (a ValueError subclass).","triggerScenarios":"Calling parse_wheel_filename() with a filename ending in '.tar.gz', '.zip', '.egg', or with no extension at all, e.g. parse_wheel_filename('foo-1.0.tar.gz') or parse_wheel_filename('foo-1.0-py3-none-any').","commonSituations":"Passing an sdist filename to the wheel parser by mistake, iterating over a mixed directory of wheels and sdists without filtering by extension, or a typo in the filename extension.","solutions":["Ensure the filename ends with '.whl' before calling parse_wheel_filename().","If the file is a source distribution (.tar.gz or .zip), call parse_sdist_filename() instead.","Strip directory components with os.path.basename() if you are passing full paths."],"exampleFix":"// before\nname, ver, build, tags = parse_wheel_filename('foo-1.0.tar.gz')\n// after\nname, ver = parse_sdist_filename('foo-1.0.tar.gz')","handlingStrategy":"validation","validationCode":"from packaging.utils import parse_wheel_filename, InvalidWheelFilename\n\ndef safe_parse_wheel(filename: str):\n    if not filename.endswith(\".whl\"):\n        raise ValueError(f\"Not a wheel file: {filename!r}\")\n    return parse_wheel_filename(filename)","typeGuard":"def is_wheel_filename(filename: str) -> bool:\n    return isinstance(filename, str) and filename.endswith(\".whl\")","tryCatchPattern":"from packaging.utils import InvalidWheelFilename\n\ntry:\n    name, ver, build, tags = parse_wheel_filename(filename)\nexcept InvalidWheelFilename as e:\n    logger.warning(\"Skipping non-wheel file %s: %s\", filename, e)","preventionTips":["Filter files by '.whl' extension before parsing","Route .tar.gz/.zip files to parse_sdist_filename()","Use os.path.basename() to isolate the filename from a path"],"tags":["wheel","filename","packaging","pep427"],"backgroundTag":null,"analyzedSha":"f399c3718970b1b0e2478dac5296eb62679a9b86","analyzedAt":"2026-08-08T23:01:42.227Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}