@@ -105,9 +105,8 @@ class FindModuleCache:
105
105
106
106
def __init__ (self ,
107
107
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 :
111
110
self .search_paths = search_paths
112
111
self .fscache = fscache or FileSystemCache ()
113
112
# Cache for get_toplevel_possibilities:
@@ -117,7 +116,6 @@ def __init__(self,
117
116
self .results = {} # type: Dict[str, ModuleSearchResult]
118
117
self .ns_ancestors = {} # type: Dict[str, str]
119
118
self .options = options
120
- self .ns_packages = ns_packages or [] # type: List[str]
121
119
122
120
def clear (self ) -> None :
123
121
self .results .clear ()
@@ -208,7 +206,7 @@ def _can_find_module_in_parent_dir(self, id: str) -> bool:
208
206
of the current working directory.
209
207
"""
210
208
working_dir = os .getcwd ()
211
- parent_search = FindModuleCache (SearchPaths ((), (), (), ()))
209
+ parent_search = FindModuleCache (SearchPaths ((), (), (), ()), self . fscache , self . options )
212
210
while any (file .endswith (("__init__.py" , "__init__.pyi" ))
213
211
for file in os .listdir (working_dir )):
214
212
working_dir = os .path .dirname (working_dir )
@@ -364,36 +362,45 @@ def find_modules_recursive(self, module: str) -> List[BuildSource]:
364
362
if isinstance (module_path , ModuleNotFoundReason ):
365
363
return []
366
364
result = [BuildSource (module_path , module , None )]
365
+
366
+ package_path = None
367
367
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 ))
397
404
return result
398
405
399
406
0 commit comments