12
12
import os
13
13
import pathlib
14
14
import sys
15
+ import types
15
16
import zipimport
16
17
from collections .abc import Iterator , Sequence
17
18
from pathlib import Path
23
24
from . import util
24
25
25
26
if sys .version_info >= (3 , 8 ):
26
- from typing import Literal
27
+ from typing import Literal , Protocol
27
28
else :
28
- from typing_extensions import Literal
29
+ from typing_extensions import Literal , Protocol
30
+
31
+
32
+ # The MetaPathFinder protocol comes from typeshed, which says:
33
+ # Intentionally omits one deprecated and one optional method of `importlib.abc.MetaPathFinder`
34
+ class _MetaPathFinder (Protocol ):
35
+ def find_spec (
36
+ self ,
37
+ fullname : str ,
38
+ path : Sequence [str ] | None ,
39
+ target : types .ModuleType | None = ...,
40
+ ) -> importlib .machinery .ModuleSpec | None :
41
+ ... # pragma: no cover
29
42
30
43
31
44
class ModuleType (enum .Enum ):
@@ -43,6 +56,15 @@ class ModuleType(enum.Enum):
43
56
PY_NAMESPACE = enum .auto ()
44
57
45
58
59
+ _MetaPathFinderModuleTypes : dict [str , ModuleType ] = {
60
+ # Finders created by setuptools editable installs
61
+ "_EditableFinder" : ModuleType .PY_SOURCE ,
62
+ "_EditableNamespaceFinder" : ModuleType .PY_NAMESPACE ,
63
+ # Finders create by six
64
+ "_SixMetaPathImporter" : ModuleType .PY_SOURCE ,
65
+ }
66
+
67
+
46
68
class ModuleSpec (NamedTuple ):
47
69
"""Defines a class similar to PEP 420's ModuleSpec.
48
70
@@ -122,8 +144,10 @@ def find_module(
122
144
try :
123
145
spec = importlib .util .find_spec (modname )
124
146
if (
125
- spec and spec .loader is importlib .machinery .FrozenImporter
126
- ): # noqa: E501 # type: ignore[comparison-overlap]
147
+ spec
148
+ and spec .loader # type: ignore[comparison-overlap] # noqa: E501
149
+ is importlib .machinery .FrozenImporter
150
+ ):
127
151
# No need for BuiltinImporter; builtins handled above
128
152
return ModuleSpec (
129
153
name = modname ,
@@ -226,7 +250,6 @@ def __init__(self, path: Sequence[str]) -> None:
226
250
super ().__init__ (path )
227
251
for entry_path in path :
228
252
if entry_path not in sys .path_importer_cache :
229
- # pylint: disable=no-member
230
253
try :
231
254
sys .path_importer_cache [entry_path ] = zipimport .zipimporter ( # type: ignore[assignment]
232
255
entry_path
@@ -310,7 +333,6 @@ def _is_setuptools_namespace(location: pathlib.Path) -> bool:
310
333
311
334
def _get_zipimporters () -> Iterator [tuple [str , zipimport .zipimporter ]]:
312
335
for filepath , importer in sys .path_importer_cache .items ():
313
- # pylint: disable-next=no-member
314
336
if isinstance (importer , zipimport .zipimporter ):
315
337
yield filepath , importer
316
338
@@ -349,7 +371,7 @@ def _find_spec_with_path(
349
371
module_parts : list [str ],
350
372
processed : list [str ],
351
373
submodule_path : Sequence [str ] | None ,
352
- ) -> tuple [Finder , ModuleSpec ]:
374
+ ) -> tuple [Finder | _MetaPathFinder , ModuleSpec ]:
353
375
for finder in _SPEC_FINDERS :
354
376
finder_instance = finder (search_path )
355
377
spec = finder_instance .find_module (
@@ -359,6 +381,43 @@ def _find_spec_with_path(
359
381
continue
360
382
return finder_instance , spec
361
383
384
+ # Support for custom finders
385
+ for meta_finder in sys .meta_path :
386
+ # See if we support the customer import hook of the meta_finder
387
+ meta_finder_name = meta_finder .__class__ .__name__
388
+ if meta_finder_name not in _MetaPathFinderModuleTypes :
389
+ # Setuptools>62 creates its EditableFinders dynamically and have
390
+ # "type" as their __class__.__name__. We check __name__ as well
391
+ # to see if we can support the finder.
392
+ try :
393
+ meta_finder_name = meta_finder .__name__
394
+ except AttributeError :
395
+ continue
396
+ if meta_finder_name not in _MetaPathFinderModuleTypes :
397
+ continue
398
+
399
+ module_type = _MetaPathFinderModuleTypes [meta_finder_name ]
400
+
401
+ # Meta path finders are supposed to have a find_spec method since
402
+ # Python 3.4. However, some third-party finders do not implement it.
403
+ # PEP302 does not refer to find_spec as well.
404
+ # See: https://github.com/PyCQA/astroid/pull/1752/
405
+ if not hasattr (meta_finder , "find_spec" ):
406
+ continue
407
+
408
+ spec = meta_finder .find_spec (modname , submodule_path )
409
+ if spec :
410
+ return (
411
+ meta_finder ,
412
+ ModuleSpec (
413
+ spec .name ,
414
+ module_type ,
415
+ spec .origin ,
416
+ spec .origin ,
417
+ spec .submodule_search_locations ,
418
+ ),
419
+ )
420
+
362
421
raise ImportError (f"No module named { '.' .join (module_parts )} " )
363
422
364
423
@@ -394,7 +453,7 @@ def find_spec(modpath: list[str], path: Sequence[str] | None = None) -> ModuleSp
394
453
_path , modname , module_parts , processed , submodule_path or path
395
454
)
396
455
processed .append (modname )
397
- if modpath :
456
+ if modpath and isinstance ( finder , Finder ) :
398
457
submodule_path = finder .contribute_to_path (spec , processed )
399
458
400
459
if spec .type == ModuleType .PKG_DIRECTORY :
0 commit comments