diff --git a/newsfragments/4856.bugfix.rst b/newsfragments/4856.bugfix.rst new file mode 100644 index 0000000000..ad0087ea00 --- /dev/null +++ b/newsfragments/4856.bugfix.rst @@ -0,0 +1,2 @@ +Fixed ``pkg_resources.require(...)`` to also consider standardised +``dist-info`` directories. diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 68feeb0593..8a2fbfa412 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -708,14 +708,19 @@ def find(self, req: Requirement) -> Distribution | None: If there is no active distribution for the requested project, ``None`` is returned. """ - dist = self.by_key.get(req.key) + dist: Distribution | None = None - if dist is None: - canonical_key = self.normalized_to_canonical_keys.get(req.key) + candidates = ( + req.key, + self.normalized_to_canonical_keys.get(req.key), + safe_name(req.key).replace(".", "-"), + ) - if canonical_key is not None: - req.key = canonical_key - dist = self.by_key.get(canonical_key) + for candidate in filter(None, candidates): + dist = self.by_key.get(candidate) + if dist: + req.key = candidate + break if dist is not None and dist not in req: # XXX add more info diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 0f696e8502..cfc9b16c0f 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -2,6 +2,7 @@ import builtins import datetime +import inspect import os import plistlib import stat @@ -425,3 +426,60 @@ def test_normalize_path_backslash_sep(self, unnormalized, expected): """Ensure path seps are cleaned on backslash path sep systems.""" result = pkg_resources.normalize_path(unnormalized) assert result.endswith(expected) + + +class TestWorkdirRequire: + def fake_site_packages(self, tmp_path, monkeypatch, dist_files): + site_packages = tmp_path / "site-packages" + site_packages.mkdir() + for file, content in self.FILES.items(): + path = site_packages / file + path.parent.mkdir(exist_ok=True, parents=True) + path.write_text(inspect.cleandoc(content), encoding="utf-8") + + monkeypatch.setattr(sys, "path", [site_packages]) + return os.fspath(site_packages) + + FILES = { + "pkg1_mod-1.2.3.dist-info/METADATA": """ + Metadata-Version: 2.4 + Name: pkg1.mod + Version: 1.2.3 + """, + "pkg2.mod-0.42.dist-info/METADATA": """ + Metadata-Version: 2.1 + Name: pkg2.mod + Version: 0.42 + """, + "pkg3_mod.egg-info/PKG-INFO": """ + Name: pkg3.mod + Version: 1.2.3.4 + """, + "pkg4.mod.egg-info/PKG-INFO": """ + Name: pkg4.mod + Version: 0.42.1 + """, + } + + @pytest.mark.parametrize( + ("version", "requirement"), + [ + ("1.2.3", "pkg1.mod>=1"), + ("0.42", "pkg2.mod>=0.4"), + ("1.2.3.4", "pkg3.mod<=2"), + ("0.42.1", "pkg4.mod>0.2,<1"), + ], + ) + def test_require_non_normalised_name( + self, tmp_path, monkeypatch, version, requirement + ): + # https://github.com/pypa/setuptools/issues/4853 + site_packages = self.fake_site_packages(tmp_path, monkeypatch, self.FILES) + ws = pkg_resources.WorkingSet([site_packages]) + + for req in [requirement, requirement.replace(".", "-")]: + [dist] = ws.require(req) + assert dist.version == version + assert os.path.samefile( + os.path.commonpath([dist.location, site_packages]), site_packages + )