10
10
import fnmatch
11
11
from functools import partial
12
12
from importlib .machinery import ModuleSpec
13
+ from importlib .machinery import PathFinder
13
14
import importlib .util
14
15
import itertools
15
16
import os
37
38
from _pytest .warning_types import PytestWarning
38
39
39
40
40
- LOCK_TIMEOUT = 60 * 60 * 24 * 3
41
+ if sys .version_info < (3 , 11 ):
42
+ from importlib ._bootstrap_external import _NamespaceLoader as NamespaceLoader
43
+ else :
44
+ from importlib .machinery import NamespaceLoader
41
45
46
+ LOCK_TIMEOUT = 60 * 60 * 24 * 3
42
47
43
48
_AnyPurePath = TypeVar ("_AnyPurePath" , bound = PurePath )
44
49
@@ -618,6 +623,34 @@ def _import_module_using_spec(
618
623
If True, will call insert_missing_modules to create empty intermediate modules
619
624
for made-up module names (when importing test files not reachable from sys.path).
620
625
"""
626
+ # Attempt to import the parent module, seems is our responsibility:
627
+ # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
628
+ parent_module_name , _ , name = module_name .rpartition ("." )
629
+ parent_module : ModuleType | None = None
630
+ if parent_module_name :
631
+ parent_module = sys .modules .get (parent_module_name )
632
+ if parent_module is None :
633
+ if (module_path .parent / "__init__.py" ).is_file ():
634
+ parent_module_location = module_location
635
+ else :
636
+ parent_module_location = module_location .parent
637
+
638
+ if module_path .name == "__init__.py" :
639
+ parent_module_path = module_path .parent .parent
640
+ else :
641
+ parent_module_path = module_path .parent
642
+
643
+ # Consider the parent module path as its __init__.py file, if it has one.
644
+ if (parent_module_path / "__init__.py" ).is_file ():
645
+ parent_module_path = parent_module_path / "__init__.py"
646
+
647
+ parent_module = _import_module_using_spec (
648
+ parent_module_name ,
649
+ parent_module_path ,
650
+ parent_module_location ,
651
+ insert_modules = insert_modules ,
652
+ )
653
+
621
654
# Checking with sys.meta_path first in case one of its hooks can import this module,
622
655
# such as our own assertion-rewrite hook.
623
656
for meta_importer in sys .meta_path :
@@ -627,36 +660,18 @@ def _import_module_using_spec(
627
660
if spec_matches_module_path (spec , module_path ):
628
661
break
629
662
else :
630
- spec = importlib .util .spec_from_file_location (module_name , str (module_path ))
663
+ loader = None
664
+ if "." in module_path .name and module_path .is_dir ():
665
+ # The `spec_from_file_location` matches a loader based on the file extension by default.
666
+ # For a namespace package, you need to manually specify a loader.
667
+ loader = NamespaceLoader (name , module_path , PathFinder ())
668
+
669
+ spec = importlib .util .spec_from_file_location (
670
+ module_name , str (module_path ), loader = loader
671
+ )
631
672
632
673
if spec_matches_module_path (spec , module_path ):
633
674
assert spec is not None
634
- # Attempt to import the parent module, seems is our responsibility:
635
- # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
636
- parent_module_name , _ , name = module_name .rpartition ("." )
637
- parent_module : ModuleType | None = None
638
- if parent_module_name :
639
- parent_module = sys .modules .get (parent_module_name )
640
- if parent_module is None :
641
- # Find the directory of this module's parent.
642
- parent_dir = (
643
- module_path .parent .parent
644
- if module_path .name == "__init__.py"
645
- else module_path .parent
646
- )
647
- # Consider the parent module path as its __init__.py file, if it has one.
648
- parent_module_path = (
649
- parent_dir / "__init__.py"
650
- if (parent_dir / "__init__.py" ).is_file ()
651
- else parent_dir
652
- )
653
- parent_module = _import_module_using_spec (
654
- parent_module_name ,
655
- parent_module_path ,
656
- parent_dir ,
657
- insert_modules = insert_modules ,
658
- )
659
-
660
675
# Find spec and import this module.
661
676
mod = importlib .util .module_from_spec (spec )
662
677
sys .modules [module_name ] = mod
@@ -669,16 +684,25 @@ def _import_module_using_spec(
669
684
if insert_modules :
670
685
insert_missing_modules (sys .modules , module_name )
671
686
return mod
672
-
673
687
return None
674
688
675
689
676
690
def spec_matches_module_path (module_spec : ModuleSpec | None , module_path : Path ) -> bool :
677
691
"""Return true if the given ModuleSpec can be used to import the given module path."""
678
- if module_spec is None or module_spec . origin is None :
692
+ if module_spec is None :
679
693
return False
680
694
681
- return Path (module_spec .origin ) == module_path
695
+ if module_spec .origin :
696
+ return Path (module_spec .origin ) == module_path
697
+
698
+ # If this is a namespace package, compare the path with the `module_spec.submodule_Search_Locations`
699
+ # https://docs.python.org/zh-cn/3/library/importlib.html#importlib.machinery.ModuleSpec.submodule_search_locations
700
+ if module_spec .submodule_search_locations :
701
+ for _path in module_spec .submodule_search_locations :
702
+ if Path (_path ) == module_path :
703
+ return True
704
+
705
+ return False
682
706
683
707
684
708
# Implement a special _is_same function on Windows which returns True if the two filenames
0 commit comments