Repository: pypa/pip
Pull request #12918 (2024-08-16 00:12:49)

Deprecate wheel filenames that are not compliant with PEP 440
Fixes https://github.com/pypa/pip/issues/12914

It should be noted that because of https://github.com/pypa/pip/pull/12327, which already uses `parse_wheel_filename` instead of `Wheel.wheel_file_re` a wheel that uses a `_` in the version part of the string will not be found using `--find-links=<path-to-dir>`. This was an unintended side effect, but no one has raised an issue about it.

Once the depreciation cycle is complete https://github.com/pypa/pip/pull/12917 can land.

I thought about merging this PR and https://github.com/pypa/pip/pull/12917  by first parsing with `parse_wheel_filename` and then falling back to `Wheel.wheel_file_re` with a depreciation error, but it introduced a lot of additional complexity.

I did some reading on the motivation to allow `_` in the wheel version part, and it seemed to be supporting tools which have long since been fixed or are themselves no longer maintained.



Patch:
diff --git a/news/12918.removal.rst b/news/12918.removal.rst
new file mode 100644
index 000000000..c5a52cf66
--- /dev/null
+++ b/news/12918.removal.rst
@@ -0,0 +1,1 @@
+Deprecate wheel filenames that are not compliant with PEP 440.
diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py
index 36d4d2e78..63db978f1 100644
--- a/src/pip/_internal/models/wheel.py
+++ b/src/pip/_internal/models/wheel.py
@@ -8,6 +8,7 @@ from typing import Dict, Iterable, List
 from pip._vendor.packaging.tags import Tag
 
 from pip._internal.exceptions import InvalidWheelFilename
+from pip._internal.utils.deprecation import deprecated
 
 
 class Wheel:
@@ -29,9 +30,25 @@ class Wheel:
             raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
         self.filename = filename
         self.name = wheel_info.group("name").replace("_", "-")
-        # we'll assume "_" means "-" due to wheel naming scheme
-        # (https://github.com/pypa/pip/issues/1150)
-        self.version = wheel_info.group("ver").replace("_", "-")
+        _version = wheel_info.group("ver")
+        if "_" in _version:
+            deprecated(
+                reason=(
+                    f"Wheel filename {filename!r} uses an invalid filename format, "
+                    f"as the version part {_version!r} is not correctly normalised, "
+                    "and contains an underscore character. Future versions of pip may "
+                    "fail to recognise this wheel."
+                ),
+                replacement=(
+                    "rename the wheel to use a correctly normalised version part "
+                    "(this may require updating the version in the project metadata)"
+                ),
+                gone_in="25.1",
+                issue=12914,
+            )
+            _version = _version.replace("_", "-")
+
+        self.version = _version
         self.build_tag = wheel_info.group("build")
         self.pyversions = wheel_info.group("pyver").split(".")
         self.abis = wheel_info.group("abi").split(".")


Test patch:
diff --git a/tests/unit/test_models_wheel.py b/tests/unit/test_models_wheel.py
index c06525e08..ee4d88c74 100644
--- a/tests/unit/test_models_wheel.py
+++ b/tests/unit/test_models_wheel.py
@@ -3,7 +3,7 @@ from pip._vendor.packaging.tags import Tag
 
 from pip._internal.exceptions import InvalidWheelFilename
 from pip._internal.models.wheel import Wheel
-from pip._internal.utils import compatibility_tags
+from pip._internal.utils import compatibility_tags, deprecation
 
 
 class TestWheelFile:
@@ -175,5 +175,6 @@ class TestWheelFile:
         Test that we convert '_' to '-' for versions parsed out of wheel
         filenames
         """
-        w = Wheel("simple-0.1_1-py2-none-any.whl")
+        with pytest.warns(deprecation.PipDeprecationWarning):
+            w = Wheel("simple-0.1_1-py2-none-any.whl")
         assert w.version == "0.1-1"