diff --git a/news/aa82171b-1578-4128-8db3-9aa72b3a6a84.trivial.rst b/news/aa82171b-1578-4128-8db3-9aa72b3a6a84.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pip/_internal/metadata/importlib/_compat.py b/src/pip/_internal/metadata/importlib/_compat.py index 593bff23ede..2ae1ca975a2 100644 --- a/src/pip/_internal/metadata/importlib/_compat.py +++ b/src/pip/_internal/metadata/importlib/_compat.py @@ -1,6 +1,9 @@ import importlib.metadata +import os from typing import Any, Optional, Protocol, cast +from pip._vendor.packaging.utils import NormalizedName, canonicalize_name + class BadMetadata(ValueError): def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None: @@ -43,13 +46,21 @@ def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]: return getattr(d, "_path", None) -def get_dist_name(dist: importlib.metadata.Distribution) -> str: - """Get the distribution's project name. +def get_dist_canonical_name(dist: importlib.metadata.Distribution) -> NormalizedName: + """Get the distribution's normalized name. The ``name`` attribute is only available in Python 3.10 or later. We are targeting exactly that, but Mypy does not know this. """ + # Try to get the name from the metadata directory name. + # This is much faster than reading metadata. + if info_location := get_info_location(dist): + stem, suffix = os.path.splitext(info_location.name) + if suffix in (".dist-info", ".egg-info"): + name = stem.split("-", 1)[0] + return canonicalize_name(name) + name = cast(Any, dist).name if not isinstance(name, str): raise BadMetadata(dist, reason="invalid metadata entry 'name'") - return name + return canonicalize_name(name) diff --git a/src/pip/_internal/metadata/importlib/_dists.py b/src/pip/_internal/metadata/importlib/_dists.py index 8591029f16e..4eb7e5c8c66 100644 --- a/src/pip/_internal/metadata/importlib/_dists.py +++ b/src/pip/_internal/metadata/importlib/_dists.py @@ -30,7 +30,7 @@ from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file -from ._compat import BasePath, get_dist_name +from ._compat import BasePath, get_dist_canonical_name class WheelDistribution(importlib.metadata.Distribution): @@ -153,25 +153,20 @@ def installed_location(self) -> Optional[str]: return None return normalize_path(str(self._installed_location)) - def _get_dist_name_from_location(self) -> Optional[str]: - """Try to get the name from the metadata directory name. - - This is much faster than reading metadata. - """ - if self._info_location is None: - return None - stem, suffix = os.path.splitext(self._info_location.name) - if suffix not in (".dist-info", ".egg-info"): - return None - return stem.split("-", 1)[0] - @property def canonical_name(self) -> NormalizedName: - name = self._get_dist_name_from_location() or get_dist_name(self._dist) - return canonicalize_name(name) + return get_dist_canonical_name(self._dist) @property def version(self) -> DistributionVersion: + # Try to get the version from the metadata directory name. + # This is much faster than reading metadata. + if self._info_location is not None: + stem, suffix = os.path.splitext(self._info_location.name) + if suffix == ".dist-info": + version = stem.split("-", 1)[1] + return parse_version(version) + return parse_version(self._dist.version) def is_file(self, path: InfoPath) -> bool: diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 048dc55dcb2..7791db32471 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -15,7 +15,7 @@ from pip._internal.utils.deprecation import deprecated from pip._internal.utils.filetypes import WHEEL_EXTENSION -from ._compat import BadMetadata, BasePath, get_dist_name, get_info_location +from ._compat import BadMetadata, BasePath, get_dist_canonical_name, get_info_location from ._dists import Distribution logger = logging.getLogger(__name__) @@ -61,14 +61,13 @@ def _find_impl(self, location: str) -> Iterator[FoundResult]: for dist in importlib.metadata.distributions(path=[location]): info_location = get_info_location(dist) try: - raw_name = get_dist_name(dist) + name = get_dist_canonical_name(dist) except BadMetadata as e: logger.warning("Skipping %s due to %s", info_location, e.reason) continue - normalized_name = canonicalize_name(raw_name) - if normalized_name in self._found_names: + if name in self._found_names: continue - self._found_names.add(normalized_name) + self._found_names.add(name) yield dist, info_location def find(self, location: str) -> Iterator[BaseDistribution]: diff --git a/tests/data/packages/simplewheel-2.0-1-py2.py3-none-any.whl b/tests/data/packages/simplewheel-2.0-1-py2.py3-none-any.whl index cd34cf8a046..ba852ba2895 100644 Binary files a/tests/data/packages/simplewheel-2.0-1-py2.py3-none-any.whl and b/tests/data/packages/simplewheel-2.0-1-py2.py3-none-any.whl differ diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index b65212f929c..a91549c3fa6 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -1172,7 +1172,7 @@ def test_install_nonlocal_compatible_wheel( ) assert result.returncode == SUCCESS - distinfo = Path("scratch") / "target" / "simplewheel-2.0-1.dist-info" + distinfo = Path("scratch") / "target" / "simplewheel-2.0.dist-info" result.did_create(distinfo) # Test install without --target diff --git a/tests/functional/test_install_report.py b/tests/functional/test_install_report.py index a1e7f8375d9..a25de64a3d1 100644 --- a/tests/functional/test_install_report.py +++ b/tests/functional/test_install_report.py @@ -39,7 +39,7 @@ def test_install_report_basic( assert url.endswith("/packages/simplewheel-2.0-1-py2.py3-none-any.whl") assert ( simplewheel_report["download_info"]["archive_info"]["hash"] - == "sha256=191d6520d0570b13580bf7642c97ddfbb46dd04da5dd2cf7bef9f32391dfe716" + == "sha256=71e1ca6b16ae3382a698c284013f66504f2581099b2ce4801f60e9536236ceee" )