Repository: pypa/setuptools

Issue:
editable_wheel is broken in 68.1.0
### setuptools version

setuptools==68.1.0

### Python version

Python 3.11

### OS

Linux

### Additional environment information

_No response_

### Description

setuptools 68.1.0 broke editable_wheels due to commit https://github.com/pypa/setuptools/commit/db3743a90b4948ca4d8435ed52bc76ca98152e57 from https://github.com/pypa/setuptools/pull/3995

To my understanding, the problem is that `_find_nested_spec` passes the wrong path to `PathFinder.find_spec(rest, path=[parent_path])`

### Expected behavior

Pass the full path to `PathFinder.find_spec`.
Doing that requires a lookup to happen first though, which pretty much defeats the purpose of db3743a90b4948ca4d8435ed52bc76ca98152e57 though as I understand it, which was to let Python handle the case insensitive lookup.

### How to Reproduce

This is how `PathFinder.find_spec` treats its input path, note how it only cares about the tail of the name to lookup:
```
python3 -vv
>>> from importlib.machinery import PathFinder
>>> PathFinder.find_spec("components.persistent_notification.loader", ["/home/erik/development/home-assistant_fork/"])
```

### Output

```
# trying /home/erik/development/home-assistant_fork/loader.cpython-311-x86_64-linux-gnu.so
# trying /home/erik/development/home-assistant_fork/loader.abi3.so
# trying /home/erik/development/home-assistant_fork/loader.so
# trying /home/erik/development/home-assistant_fork/loader.py
# trying /home/erik/development/home-assistant_fork/loader.pyc
```



Discussion:
Hi @emontnemery, thank you for opening the issue.

Could you please provide the [full reproducer](https://stackoverflow.com/help/minimal-reproducible-example)? In your example it is not clear where the `home-assistant_fork` comes from, or `components.persistent_notification.loader`...

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -232,7 +232,7 @@
 html_static_path = ['images']  # should contain the folder with icons
 
 # Add support for nice Not Found 404 pages
-extensions += ['notfound.extension']
+# extensions += ['notfound.extension']  # readthedocs/sphinx-notfound-page#219
 
 # List of dicts with <link> HTML attributes
 # static-file points to files in the html_static_path (href is computed)
diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py
--- a/setuptools/command/editable_wheel.py
+++ b/setuptools/command/editable_wheel.py
@@ -782,11 +782,15 @@ def find_spec(cls, fullname, path=None, target=None):
             pkg_path = MAPPING[fullname]
             return cls._find_spec(fullname, Path(pkg_path))
 
-        # Nested modules (apparently required for namespaces to work)
-        for pkg, pkg_path in reversed(list(MAPPING.items())):
-            if fullname.startswith(f"{{pkg}}."):
-                return cls._find_nested_spec(fullname, pkg, pkg_path)
-
+        # Handle immediate children modules (required for namespaces to work)
+        # To avoid problems with case sensitivity in the file system we delegate
+        # to the importlib.machinery implementation.
+        parent, _, child = fullname.rpartition(".")
+        if parent and parent in MAPPING:
+            return PathFinder.find_spec(fullname, path=[MAPPING[parent]])
+
+        # Other levels of nesting should be handled automatically by importlib
+        # using the parent path.
         return None
 
     @classmethod
@@ -797,20 +801,6 @@ def _find_spec(cls, fullname, candidate_path):
             if candidate.exists():
                 return spec_from_file_location(fullname, candidate)
 
-    @classmethod
-    def _find_nested_spec(cls, fullname, parent, parent_path):
-        '''
-        To avoid problems with case sensitivity in the file system we delegate to the
-        importlib.machinery implementation.
-        '''
-        rest = fullname.replace(parent, "", 1).strip(".")
-        nested = PathFinder.find_spec(rest, path=[parent_path])
-        return nested and spec_from_file_location(
-            fullname,
-            nested.origin,
-            submodule_search_locations=nested.submodule_search_locations
-        )
-
 
 class _EditableNamespaceFinder:  # PathEntryFinder
     @classmethod


Test patch:
diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py
--- a/setuptools/tests/test_editable_install.py
+++ b/setuptools/tests/test_editable_install.py
@@ -660,6 +660,50 @@ def test_namespace_case_sensitivity(self, tmp_path):
             with pytest.raises(ImportError, match="\'ns\\.othername\\.foo\\.BAR\\'"):
                 import_module("ns.othername.foo.BAR")
 
+    def test_intermediate_packages(self, tmp_path):
+        """
+        The finder should not import ``fullname`` if the intermediate segments
+        don't exist (see pypa/setuptools#4019).
+        """
+        files = {
+            "src": {
+                "mypkg": {
+                    "__init__.py": "",
+                    "config.py": "a = 13",
+                    "helloworld.py": "b = 13",
+                    "components": {
+                        "config.py": "a = 37",
+                    },
+                },
+            }
+        }
+        jaraco.path.build(files, prefix=tmp_path)
+
+        mapping = {"mypkg": str(tmp_path / "src/mypkg")}
+        template = _finder_template(str(uuid4()), mapping, {})
+
+        with contexts.save_paths(), contexts.save_sys_modules():
+            for mod in (
+                "mypkg",
+                "mypkg.config",
+                "mypkg.helloworld",
+                "mypkg.components",
+                "mypkg.components.config",
+                "mypkg.components.helloworld",
+            ):
+                sys.modules.pop(mod, None)
+
+            self.install_finder(template)
+
+            config = import_module("mypkg.components.config")
+            assert config.a == 37
+
+            helloworld = import_module("mypkg.helloworld")
+            assert helloworld.b == 13
+
+            with pytest.raises(ImportError):
+                import_module("mypkg.components.helloworld")
+
 
 def test_pkg_roots(tmp_path):
     """This test focus in getting a particular implementation detail right.