Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update WorkingSet.find to consider standardised .dist-info directory names. #4856

Merged
merged 7 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions newsfragments/4856.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed ``pkg_resources.require(...)`` to also consider standardised
``dist-info`` directories.
17 changes: 11 additions & 6 deletions pkg_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions pkg_resources/tests/test_pkg_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import builtins
import datetime
import inspect
import os
import plistlib
import stat
Expand Down Expand Up @@ -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
)
Loading