Skip to content

Commit 19b63d1

Browse files
committed
Fix PathEntryFinder / MetaPathFinder in editable_wheel
It seems that the import machinery skips PathEntryFinder when trying to locate nested namespaces, if the `sys.path_hook` item corresponding to the finder cannot be located in the submodule locations of the parent namespace. This means that we should probably always add the PATH_PLACEHOLDER to the namespace spec. This PR also add some type hints to the template, because it helped to debug some type errors.
1 parent a39d362 commit 19b63d1

File tree

1 file changed

+19
-13
lines changed

1 file changed

+19
-13
lines changed

setuptools/command/editable_wheel.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -793,23 +793,22 @@ def _get_root(self):
793793

794794

795795
_FINDER_TEMPLATE = """\
796+
from __future__ import annotations
796797
import sys
797798
from importlib.machinery import ModuleSpec, PathFinder
798799
from importlib.machinery import all_suffixes as module_suffixes
799800
from importlib.util import spec_from_file_location
800801
from itertools import chain
801802
from pathlib import Path
802803
803-
MAPPING = {mapping!r}
804-
NAMESPACES = {namespaces!r}
804+
MAPPING: dict[str, str] = {mapping!r}
805+
NAMESPACES: dict[str, list[str]] = {namespaces!r}
805806
PATH_PLACEHOLDER = {name!r} + ".__path_hook__"
806807
807808
808809
class _EditableFinder: # MetaPathFinder
809810
@classmethod
810-
def find_spec(cls, fullname, path=None, target=None):
811-
extra_path = []
812-
811+
def find_spec(cls, fullname: str, _path=None, _target=None) -> ModuleSpec | None:
813812
# Top-level packages and modules (we know these exist in the FS)
814813
if fullname in MAPPING:
815814
pkg_path = MAPPING[fullname]
@@ -820,43 +819,50 @@ def find_spec(cls, fullname, path=None, target=None):
820819
# to the importlib.machinery implementation.
821820
parent, _, child = fullname.rpartition(".")
822821
if parent and parent in MAPPING:
823-
return PathFinder.find_spec(fullname, path=[MAPPING[parent], *extra_path])
822+
return PathFinder.find_spec(fullname, path=[MAPPING[parent]])
824823
825824
# Other levels of nesting should be handled automatically by importlib
826825
# using the parent path.
827826
return None
828827
829828
@classmethod
830-
def _find_spec(cls, fullname, candidate_path):
829+
def _find_spec(cls, fullname: str, candidate_path: Path) -> ModuleSpec | None:
831830
init = candidate_path / "__init__.py"
832831
candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
833832
for candidate in chain([init], candidates):
834833
if candidate.exists():
835834
return spec_from_file_location(fullname, candidate)
835+
return None
836836
837837
838838
class _EditableNamespaceFinder: # PathEntryFinder
839839
@classmethod
840-
def _path_hook(cls, path):
840+
def _path_hook(cls, path) -> type[_EditableNamespaceFinder]:
841841
if path == PATH_PLACEHOLDER:
842842
return cls
843843
raise ImportError
844844
845845
@classmethod
846-
def _paths(cls, fullname):
847-
# Ensure __path__ is not empty for the spec to be considered a namespace.
848-
return NAMESPACES[fullname] or MAPPING.get(fullname) or [PATH_PLACEHOLDER]
846+
def _paths(cls, fullname: str) -> list[str]:
847+
paths = NAMESPACES[fullname]
848+
if not paths and fullname in MAPPING:
849+
paths = [MAPPING[fullname]]
850+
# Always add placeholder, for 2 reasons:
851+
# 1. __path__ cannot be empty for the spec to be considered namespace.
852+
# 2. In the case of nested namespaces, we need to force
853+
# import machinery to query _EditableNamespaceFinder again.
854+
return [*paths, PATH_PLACEHOLDER]
849855
850856
@classmethod
851-
def find_spec(cls, fullname, target=None):
857+
def find_spec(cls, fullname: str, _target=None) -> ModuleSpec | None:
852858
if fullname in NAMESPACES:
853859
spec = ModuleSpec(fullname, None, is_package=True)
854860
spec.submodule_search_locations = cls._paths(fullname)
855861
return spec
856862
return None
857863
858864
@classmethod
859-
def find_module(cls, fullname):
865+
def find_module(cls, _fullname) -> None:
860866
return None
861867
862868

0 commit comments

Comments
 (0)