@@ -505,7 +505,7 @@ def __init__(self, dist: Distribution, name: str):
505
505
self .dist = dist
506
506
self .name = name
507
507
508
- def __call__ (self , wheel : "WheelFile" , files : List [str ], mapping : Dict [str , str ]) :
508
+ def template_vars (self ) -> Tuple [ str , str , Dict [str , str ], Dict [str , List [ str ]]] :
509
509
src_root = self .dist .src_root or os .curdir
510
510
top_level = chain (_find_packages (self .dist ), _find_top_level_modules (self .dist ))
511
511
package_dir = self .dist .package_dir or {}
@@ -519,7 +519,7 @@ def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]
519
519
)
520
520
521
521
legacy_namespaces = {
522
- pkg : find_package_path (pkg , roots , self .dist .src_root or "" )
522
+ cast ( str , pkg ) : find_package_path (pkg , roots , self .dist .src_root or "" )
523
523
for pkg in self .dist .namespace_packages or []
524
524
}
525
525
@@ -530,11 +530,20 @@ def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]
530
530
531
531
name = f"__editable__.{ self .name } .finder"
532
532
finder = _normalization .safe_identifier (name )
533
+ return finder , name , mapping , namespaces_
534
+
535
+ def get_implementation (self ) -> Iterator [Tuple [str , bytes ]]:
536
+ finder , name , mapping , namespaces_ = self .template_vars ()
537
+
533
538
content = bytes (_finder_template (name , mapping , namespaces_ ), "utf-8" )
534
- wheel . writestr (f"{ finder } .py" , content )
539
+ yield (f"{ finder } .py" , content )
535
540
536
541
content = _encode_pth (f"import { finder } ; { finder } .install()" )
537
- wheel .writestr (f"__editable__.{ self .name } .pth" , content )
542
+ yield (f"__editable__.{ self .name } .pth" , content )
543
+
544
+ def __call__ (self , wheel : "WheelFile" , files : List [str ], mapping : Dict [str , str ]):
545
+ for file , content in self .get_implementation ():
546
+ wheel .writestr (file , content )
538
547
539
548
def __enter__ (self ):
540
549
msg = "Editable install will be performed using a meta path finder.\n "
@@ -784,23 +793,22 @@ def _get_root(self):
784
793
785
794
786
795
_FINDER_TEMPLATE = """\
796
+ from __future__ import annotations
787
797
import sys
788
798
from importlib.machinery import ModuleSpec, PathFinder
789
799
from importlib.machinery import all_suffixes as module_suffixes
790
800
from importlib.util import spec_from_file_location
791
801
from itertools import chain
792
802
from pathlib import Path
793
803
794
- MAPPING = {mapping!r}
795
- NAMESPACES = {namespaces!r}
804
+ MAPPING: dict[str, str] = {mapping!r}
805
+ NAMESPACES: dict[str, list[str]] = {namespaces!r}
796
806
PATH_PLACEHOLDER = {name!r} + ".__path_hook__"
797
807
798
808
799
809
class _EditableFinder: # MetaPathFinder
800
810
@classmethod
801
- def find_spec(cls, fullname, path=None, target=None):
802
- extra_path = []
803
-
811
+ def find_spec(cls, fullname: str, _path=None, _target=None) -> ModuleSpec | None:
804
812
# Top-level packages and modules (we know these exist in the FS)
805
813
if fullname in MAPPING:
806
814
pkg_path = MAPPING[fullname]
@@ -811,43 +819,50 @@ def find_spec(cls, fullname, path=None, target=None):
811
819
# to the importlib.machinery implementation.
812
820
parent, _, child = fullname.rpartition(".")
813
821
if parent and parent in MAPPING:
814
- return PathFinder.find_spec(fullname, path=[MAPPING[parent], *extra_path ])
822
+ return PathFinder.find_spec(fullname, path=[MAPPING[parent]])
815
823
816
824
# Other levels of nesting should be handled automatically by importlib
817
825
# using the parent path.
818
826
return None
819
827
820
828
@classmethod
821
- def _find_spec(cls, fullname, candidate_path) :
829
+ def _find_spec(cls, fullname: str , candidate_path: Path) -> ModuleSpec | None :
822
830
init = candidate_path / "__init__.py"
823
831
candidates = (candidate_path.with_suffix(x) for x in module_suffixes())
824
832
for candidate in chain([init], candidates):
825
833
if candidate.exists():
826
834
return spec_from_file_location(fullname, candidate)
835
+ return None
827
836
828
837
829
838
class _EditableNamespaceFinder: # PathEntryFinder
830
839
@classmethod
831
- def _path_hook(cls, path):
840
+ def _path_hook(cls, path) -> type[_EditableNamespaceFinder] :
832
841
if path == PATH_PLACEHOLDER:
833
842
return cls
834
843
raise ImportError
835
844
836
845
@classmethod
837
- def _paths(cls, fullname):
838
- # Ensure __path__ is not empty for the spec to be considered a namespace.
839
- 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]
840
855
841
856
@classmethod
842
- def find_spec(cls, fullname, target =None):
857
+ def find_spec(cls, fullname: str, _target =None) -> ModuleSpec | None :
843
858
if fullname in NAMESPACES:
844
859
spec = ModuleSpec(fullname, None, is_package=True)
845
860
spec.submodule_search_locations = cls._paths(fullname)
846
861
return spec
847
862
return None
848
863
849
864
@classmethod
850
- def find_module(cls, fullname) :
865
+ def find_module(cls, _fullname) -> None :
851
866
return None
852
867
853
868
0 commit comments