diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 0d65ce35f37..ae98005ceaf 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -528,11 +528,7 @@ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: ) if self._prefer_binary: binary_preference = 1 - if wheel.build_tag is not None: - match = re.match(r"^(\d+)(.*)$", wheel.build_tag) - assert match is not None, "guaranteed by filename validation" - build_tag_groups = match.groups() - build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) + build_tag = wheel.build_tag else: # sdist pri = -(support_num) has_allowed_hash = int(link.is_hash_allowed(self._hashes)) diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 70cb7a6009a..a6e962713f8 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -8,10 +8,13 @@ import zipimport from typing import Iterator, List, Optional, Sequence, Set, Tuple -from pip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pip._vendor.packaging.utils import ( + NormalizedName, + canonicalize_name, + parse_wheel_filename, +) from pip._internal.metadata.base import BaseDistribution, BaseEnvironment -from pip._internal.models.wheel import Wheel from pip._internal.utils.deprecation import deprecated from pip._internal.utils.filetypes import WHEEL_EXTENSION @@ -26,7 +29,7 @@ def _looks_like_wheel(location: str) -> bool: return False if not os.path.isfile(location): return False - if not Wheel.wheel_file_re.match(os.path.basename(location)): + if not parse_wheel_filename(os.path.basename(location)): return False return zipfile.is_zipfile(location) diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py index 36d4d2e785c..ea2f446823a 100644 --- a/src/pip/_internal/models/wheel.py +++ b/src/pip/_internal/models/wheel.py @@ -2,10 +2,13 @@ name that have meaning. """ -import re from typing import Dict, Iterable, List from pip._vendor.packaging.tags import Tag +from pip._vendor.packaging.utils import ( + InvalidWheelFilename as _PackagingInvalidWheelFilename, +) +from pip._vendor.packaging.utils import parse_wheel_filename from pip._internal.exceptions import InvalidWheelFilename @@ -13,34 +16,15 @@ class Wheel: """A wheel file""" - wheel_file_re = re.compile( - r"""^(?P(?P[^\s-]+?)-(?P[^\s-]*?)) - ((-(?P\d[^-]*?))?-(?P[^\s-]+?)-(?P[^\s-]+?)-(?P[^\s-]+?) - \.whl|\.dist-info)$""", - re.VERBOSE, - ) - def __init__(self, filename: str) -> None: - """ - :raises InvalidWheelFilename: when the filename is invalid for a wheel - """ - wheel_info = self.wheel_file_re.match(filename) - if not wheel_info: - 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("_", "-") - self.build_tag = wheel_info.group("build") - self.pyversions = wheel_info.group("pyver").split(".") - self.abis = wheel_info.group("abi").split(".") - self.plats = wheel_info.group("plat").split(".") - - # All the tag combinations from this file - self.file_tags = { - Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats - } + try: + wheel_info = parse_wheel_filename(filename) + except _PackagingInvalidWheelFilename as e: + raise InvalidWheelFilename(e.args[0]) from None + + self.name, _version, self.build_tag, self.file_tags = wheel_info + self.version = str(_version) def get_formatted_file_tags(self) -> List[str]: """Return the wheel's tags as a sorted list of strings.""" diff --git a/tests/unit/test_models_wheel.py b/tests/unit/test_models_wheel.py index c06525e089e..8aa8aa3bf82 100644 --- a/tests/unit/test_models_wheel.py +++ b/tests/unit/test_models_wheel.py @@ -11,17 +11,24 @@ def test_std_wheel_pattern(self) -> None: w = Wheel("simple-1.1.1-py2-none-any.whl") assert w.name == "simple" assert w.version == "1.1.1" - assert w.pyversions == ["py2"] - assert w.abis == ["none"] - assert w.plats == ["any"] + assert w.build_tag == () + assert w.file_tags == frozenset( + [Tag(interpreter="py2", abi="none", platform="any")] + ) def test_wheel_pattern_multi_values(self) -> None: w = Wheel("simple-1.1-py2.py3-abi1.abi2-any.whl") assert w.name == "simple" assert w.version == "1.1" - assert w.pyversions == ["py2", "py3"] - assert w.abis == ["abi1", "abi2"] - assert w.plats == ["any"] + assert w.build_tag == () + assert w.file_tags == frozenset( + [ + Tag(interpreter="py2", abi="abi1", platform="any"), + Tag(interpreter="py2", abi="abi2", platform="any"), + Tag(interpreter="py3", abi="abi1", platform="any"), + Tag(interpreter="py3", abi="abi2", platform="any"), + ] + ) def test_wheel_with_build_tag(self) -> None: # pip doesn't do anything with build tags, but theoretically, we might @@ -29,17 +36,18 @@ def test_wheel_with_build_tag(self) -> None: w = Wheel("simple-1.1-4-py2-none-any.whl") assert w.name == "simple" assert w.version == "1.1" - assert w.pyversions == ["py2"] - assert w.abis == ["none"] - assert w.plats == ["any"] + assert w.build_tag == (4, "") + assert w.file_tags == frozenset( + [Tag(interpreter="py2", abi="none", platform="any")] + ) def test_single_digit_version(self) -> None: w = Wheel("simple-1-py2-none-any.whl") assert w.version == "1" def test_non_pep440_version(self) -> None: - w = Wheel("simple-_invalid_-py2-none-any.whl") - assert w.version == "-invalid-" + with pytest.raises(InvalidWheelFilename): + Wheel("simple-_invalid_-py2-none-any.whl") def test_missing_version_raises(self) -> None: with pytest.raises(InvalidWheelFilename): @@ -172,8 +180,7 @@ def test_support_index_min__none_supported(self) -> None: def test_version_underscore_conversion(self) -> None: """ - Test that we convert '_' to '-' for versions parsed out of wheel - filenames + '_' is not PEP 440 compliant in the version part of a wheel filename """ - w = Wheel("simple-0.1_1-py2-none-any.whl") - assert w.version == "0.1-1" + with pytest.raises(InvalidWheelFilename): + Wheel("simple-0.1_1-py2-none-any.whl")