Repository: pypa/setuptools (python)

Issue:
[BUG] packages of the form package.something renamed to package_something in 75.8.1
### setuptools version

setuptools==75.8.1

### Python version

python3.10

### OS

windows

### Additional environment information




### Description

We have packages using pyproject.toml of the form:

`something.module` that use implicit namespaces for `something`

The latest 75.8.1 seems to rename the `something.module-1.2.3.dist-info` to be instead: `something_module-1.2.3.dist-info`

rolling back to 75.8.0 this problem does not exist.

This breaks folks using something of the form `pkg_resources.require('something.module')` as it now only works if they have `pkg_resources.require('something-module')`

### Expected behavior

packages should continue to have the `something.module` form

### How to Reproduce

```
git clone https://github.com/krpatter-intc/setuptools-example.git
cd setuptools-example
python -m build
```
And you'll note the naming difference in the wheel (and the .dist-info file)

You can then run:

```
import pkg_resources
pkg_resources.require("something.module") # this now throws an exception
```


### Output

```console

```

I'm aware pkg_resources is on its way out, but this still feels bad that the package has a new name. I'm not sure what the new equivalent/replacement for `pkg_resources.require` is.


Pull request:
Update `WorkingSet.find` to consider standardised `.dist-info` directory names.
<!-- First time contributors: Take a moment to review https://setuptools.pypa.io/en/latest/development/developer-guide.html! -->
<!-- Remove sections if not applicable -->

## Summary of changes

Alternative to https://github.com/pypa/setuptools/pull/4855

Closes #4853.

/cc @di: We wore working kind of in parallel on this. What are your thoughts? I don't have any strong preference regarding the 2 proposed approaches (either #4855 or #4856 are fine to me).

### Pull Request Checklist
- [x] Changes have tests
- [ ] News fragment added in [`newsfragments/`].
  _(See [documentation][PR docs] for details)_


[`newsfragments/`]: https://github.com/pypa/setuptools/tree/master/newsfragments
[PR docs]:
https://setuptools.pypa.io/en/latest/development/developer-guide.html#making-a-pull-request


Patch:
diff --git a/newsfragments/4856.bugfix.rst b/newsfragments/4856.bugfix.rst
new file mode 100644
index 000000000..ad0087ea0
--- /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 68feeb059..8a2fbfa41 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -708,14 +708,19 @@ class WorkingSet:
         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


Test patch:
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
index 0f696e850..cfc9b16c0 100644
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import builtins
 import datetime
+import inspect
 import os
 import plistlib
 import stat
@@ -425,3 +426,60 @@ class TestDeepVersionLookupDistutils:
         """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
+            )