@@ -793,23 +793,22 @@ def _get_root(self):
793
793
794
794
795
795
_FINDER_TEMPLATE = """\
796
+ from __future__ import annotations
796
797
import sys
797
798
from importlib.machinery import ModuleSpec, PathFinder
798
799
from importlib.machinery import all_suffixes as module_suffixes
799
800
from importlib.util import spec_from_file_location
800
801
from itertools import chain
801
802
from pathlib import Path
802
803
803
- MAPPING = {mapping!r}
804
- NAMESPACES = {namespaces!r}
804
+ MAPPING: dict[str, str] = {mapping!r}
805
+ NAMESPACES: dict[str, list[str]] = {namespaces!r}
805
806
PATH_PLACEHOLDER = {name!r} + ".__path_hook__"
806
807
807
808
808
809
class _EditableFinder: # MetaPathFinder
809
810
@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:
813
812
# Top-level packages and modules (we know these exist in the FS)
814
813
if fullname in MAPPING:
815
814
pkg_path = MAPPING[fullname]
@@ -820,43 +819,50 @@ def find_spec(cls, fullname, path=None, target=None):
820
819
# to the importlib.machinery implementation.
821
820
parent, _, child = fullname.rpartition(".")
822
821
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]])
824
823
825
824
# Other levels of nesting should be handled automatically by importlib
826
825
# using the parent path.
827
826
return None
828
827
829
828
@classmethod
830
- def _find_spec(cls, fullname, candidate_path) :
829
+ def _find_spec(cls, fullname: str , candidate_path: Path) -> ModuleSpec | None :
831
830
init = candidate_path / "__init__.py"
832
831
candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
833
832
for candidate in chain([init], candidates):
834
833
if candidate.exists():
835
834
return spec_from_file_location(fullname, candidate)
835
+ return None
836
836
837
837
838
838
class _EditableNamespaceFinder: # PathEntryFinder
839
839
@classmethod
840
- def _path_hook(cls, path):
840
+ def _path_hook(cls, path) -> type[_EditableNamespaceFinder] :
841
841
if path == PATH_PLACEHOLDER:
842
842
return cls
843
843
raise ImportError
844
844
845
845
@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]
849
855
850
856
@classmethod
851
- def find_spec(cls, fullname, target =None):
857
+ def find_spec(cls, fullname: str, _target =None) -> ModuleSpec | None :
852
858
if fullname in NAMESPACES:
853
859
spec = ModuleSpec(fullname, None, is_package=True)
854
860
spec.submodule_search_locations = cls._paths(fullname)
855
861
return spec
856
862
return None
857
863
858
864
@classmethod
859
- def find_module(cls, fullname) :
865
+ def find_module(cls, _fullname) -> None :
860
866
return None
861
867
862
868
0 commit comments