@@ -105,9 +105,8 @@ class FindModuleCache:
105105
106106 def __init__ (self ,
107107 search_paths : SearchPaths ,
108- fscache : Optional [FileSystemCache ] = None ,
109- options : Optional [Options ] = None ,
110- ns_packages : Optional [List [str ]] = None ) -> None :
108+ fscache : Optional [FileSystemCache ],
109+ options : Optional [Options ]) -> None :
111110 self .search_paths = search_paths
112111 self .fscache = fscache or FileSystemCache ()
113112 # Cache for get_toplevel_possibilities:
@@ -117,7 +116,6 @@ def __init__(self,
117116 self .results = {} # type: Dict[str, ModuleSearchResult]
118117 self .ns_ancestors = {} # type: Dict[str, str]
119118 self .options = options
120- self .ns_packages = ns_packages or [] # type: List[str]
121119
122120 def clear (self ) -> None :
123121 self .results .clear ()
@@ -208,7 +206,7 @@ def _can_find_module_in_parent_dir(self, id: str) -> bool:
208206 of the current working directory.
209207 """
210208 working_dir = os .getcwd ()
211- parent_search = FindModuleCache (SearchPaths ((), (), (), ()))
209+ parent_search = FindModuleCache (SearchPaths ((), (), (), ()), self . fscache , self . options )
212210 while any (file .endswith (("__init__.py" , "__init__.pyi" ))
213211 for file in os .listdir (working_dir )):
214212 working_dir = os .path .dirname (working_dir )
@@ -364,36 +362,45 @@ def find_modules_recursive(self, module: str) -> List[BuildSource]:
364362 if isinstance (module_path , ModuleNotFoundReason ):
365363 return []
366364 result = [BuildSource (module_path , module , None )]
365+
366+ package_path = None
367367 if module_path .endswith (('__init__.py' , '__init__.pyi' )):
368- # Subtle: this code prefers the .pyi over the .py if both
369- # exists, and also prefers packages over modules if both x/
370- # and x.py* exist. How? We sort the directory items, so x
371- # comes before x.py and x.pyi. But the preference for .pyi
372- # over .py is encoded in find_module(); even though we see
373- # x.py before x.pyi, find_module() will find x.pyi first. We
374- # use hits to avoid adding it a second time when we see x.pyi.
375- # This also avoids both x.py and x.pyi when x/ was seen first.
376- hits = set () # type: Set[str]
377- for item in sorted (self .fscache .listdir (os .path .dirname (module_path ))):
378- abs_path = os .path .join (os .path .dirname (module_path ), item )
379- if os .path .isdir (abs_path ) and \
380- (os .path .isfile (os .path .join (abs_path , '__init__.py' )) or
381- os .path .isfile (os .path .join (abs_path , '__init__.pyi' ))):
382- hits .add (item )
383- result += self .find_modules_recursive (module + '.' + item )
384- elif item != '__init__.py' and item != '__init__.pyi' and \
385- item .endswith (('.py' , '.pyi' )):
386- mod = item .split ('.' )[0 ]
387- if mod not in hits :
388- hits .add (mod )
389- result += self .find_modules_recursive (module + '.' + mod )
390- elif os .path .isdir (module_path ):
391- # Even subtler: handle recursive decent into PEP 420
392- # namespace packages that are explicitly listed on the command
393- # line with -p/--packages.
394- for item in sorted (self .fscache .listdir (module_path )):
395- item , _ = os .path .splitext (item )
396- result += self .find_modules_recursive (module + '.' + item )
368+ package_path = os .path .dirname (module_path )
369+ elif self .fscache .isdir (module_path ):
370+ package_path = module_path
371+ if package_path is None :
372+ return result
373+
374+ # This logic closely mirrors that in find_sources. One small but important difference is
375+ # that we do not sort names with keyfunc. The recursive call to find_modules_recursive
376+ # calls find_module, which will handle the preference between packages, pyi and py.
377+ # Another difference is it doesn't handle nested search paths / package roots.
378+
379+ seen = set () # type: Set[str]
380+ names = sorted (self .fscache .listdir (package_path ))
381+ for name in names :
382+ # Skip certain names altogether
383+ if name == '__pycache__' or name .startswith ('.' ) or name .endswith ('~' ):
384+ continue
385+ path = os .path .join (package_path , name )
386+
387+ if self .fscache .isdir (path ):
388+ # Only recurse into packages
389+ if (self .options and self .options .namespace_packages ) or (
390+ self .fscache .isfile (os .path .join (path , "__init__.py" ))
391+ or self .fscache .isfile (os .path .join (path , "__init__.pyi" ))
392+ ):
393+ seen .add (name )
394+ result .extend (self .find_modules_recursive (module + '.' + name ))
395+ else :
396+ stem , suffix = os .path .splitext (name )
397+ if stem == '__init__' :
398+ continue
399+ if stem not in seen and '.' not in stem and suffix in PYTHON_EXTENSIONS :
400+ # (If we sorted names) we could probably just make the BuildSource ourselves,
401+ # but this ensures compatibility with find_module / the cache
402+ seen .add (stem )
403+ result .extend (self .find_modules_recursive (module + '.' + stem ))
397404 return result
398405
399406
0 commit comments