1010import importlib .abc
1111import importlib .machinery
1212import importlib .util
13+ import inspect
1314import json
1415import os
1516import pathlib
@@ -182,7 +183,7 @@ def build_module_spec(cls: type, name: str, path: str, tree: Optional[Node]) ->
182183 spec = importlib .machinery .ModuleSpec (name , loader , origin = path )
183184 spec .has_location = True
184185 if loader .is_package (name ):
185- spec .submodule_search_locations = []
186+ spec .submodule_search_locations = [os . path . join ( __file__ , name ) ]
186187 return spec
187188
188189
@@ -253,6 +254,35 @@ def collect(install_plan: Dict[str, Dict[str, Any]]) -> Node:
253254 tree [path .parts [1 :]] = src
254255 return tree
255256
257+ def find_spec (fullname : str , tree : Node ) -> Optional [importlib .machinery .ModuleSpec ]:
258+ namespace = False
259+ parts = fullname .split ('.' )
260+
261+ # look for a package
262+ package = tree .get (tuple (parts ))
263+ if isinstance (package , Node ):
264+ for loader , suffix in LOADERS :
265+ src = package .get ('__init__' + suffix )
266+ if isinstance (src , str ):
267+ return build_module_spec (loader , fullname , src , package )
268+ else :
269+ namespace = True
270+
271+ # look for a module
272+ for loader , suffix in LOADERS :
273+ src = tree .get ((* parts [:- 1 ], parts [- 1 ] + suffix ))
274+ if isinstance (src , str ):
275+ return build_module_spec (loader , fullname , src , None )
276+
277+ # namespace
278+ if namespace :
279+ spec = importlib .machinery .ModuleSpec (fullname , None , is_package = True )
280+ assert isinstance (spec .submodule_search_locations , list ) # make mypy happy
281+ spec .submodule_search_locations .append (os .path .join (__file__ , fullname ))
282+ return spec
283+
284+ return None
285+
256286
257287class MesonpyMetaFinder (importlib .abc .MetaPathFinder ):
258288 def __init__ (self , names : Set [str ], path : str , cmd : List [str ], verbose : bool = False ):
@@ -271,40 +301,12 @@ def find_spec(
271301 path : Optional [Sequence [Union [bytes , str ]]] = None ,
272302 target : Optional [ModuleType ] = None
273303 ) -> Optional [importlib .machinery .ModuleSpec ]:
274-
275- if fullname .split ('.' , maxsplit = 1 )[0 ] not in self ._top_level_modules :
304+ if fullname .split ('.' , 1 )[0 ] not in self ._top_level_modules :
276305 return None
277-
278306 if self ._build_path in os .environ .get (MARKER , '' ).split (os .pathsep ):
279307 return None
280-
281- namespace = False
282308 tree = self ._rebuild ()
283- parts = fullname .split ('.' )
284-
285- # look for a package
286- package = tree .get (tuple (parts ))
287- if isinstance (package , Node ):
288- for loader , suffix in LOADERS :
289- src = package .get ('__init__' + suffix )
290- if isinstance (src , str ):
291- return build_module_spec (loader , fullname , src , package )
292- else :
293- namespace = True
294-
295- # look for a module
296- for loader , suffix in LOADERS :
297- src = tree .get ((* parts [:- 1 ], parts [- 1 ] + suffix ))
298- if isinstance (src , str ):
299- return build_module_spec (loader , fullname , src , None )
300-
301- # namespace
302- if namespace :
303- spec = importlib .machinery .ModuleSpec (fullname , None )
304- spec .submodule_search_locations = []
305- return spec
306-
307- return None
309+ return find_spec (fullname , tree )
308310
309311 @functools .lru_cache (maxsize = 1 )
310312 def _rebuild (self ) -> Node :
@@ -327,6 +329,44 @@ def _rebuild(self) -> Node:
327329 install_plan = json .load (f )
328330 return collect (install_plan )
329331
332+ def _path_hook (self , path : str ) -> MesonpyPathFinder :
333+ if os .altsep :
334+ path .replace (os .altsep , os .sep )
335+ path , _ , key = path .rpartition (os .sep )
336+ if path == __file__ :
337+ tree = self ._rebuild ()
338+ node = tree .get (tuple (key .split ('.' )))
339+ if isinstance (node , Node ):
340+ return MesonpyPathFinder (node )
341+ raise ImportError
342+
343+
344+ class MesonpyPathFinder (importlib .abc .PathEntryFinder ):
345+ def __init__ (self , tree : Node ):
346+ self ._tree = tree
347+
348+ def find_spec (self , fullname : str , target : Optional [ModuleType ] = None ) -> Optional [importlib .machinery .ModuleSpec ]:
349+ return find_spec (fullname , self ._tree )
350+
351+ def iter_modules (self , prefix : str ) -> Iterator [Tuple [str , bool ]]:
352+ yielded = set ()
353+ for name , node in self ._tree .items ():
354+ modname = inspect .getmodulename (name )
355+ if modname == '__init__' or modname in yielded :
356+ continue
357+ if isinstance (node , Node ):
358+ modname = name
359+ for _ , suffix in LOADERS :
360+ src = node .get ('__init__' + suffix )
361+ if isinstance (src , str ):
362+ yielded .add (modname )
363+ yield prefix + modname , True
364+ elif modname and '.' not in modname :
365+ yielded .add (modname )
366+ yield prefix + modname , False
367+
330368
331369def install (names : Set [str ], path : str , cmd : List [str ], verbose : bool ) -> None :
332- sys .meta_path .insert (0 , MesonpyMetaFinder (names , path , cmd , verbose ))
370+ finder = MesonpyMetaFinder (names , path , cmd , verbose )
371+ sys .meta_path .insert (0 , finder )
372+ sys .path_hooks .insert (0 , finder ._path_hook )
0 commit comments