Skip to content

Commit 815f4e1

Browse files
committed
importlib: _import_module_using_spec also imports parent modules
According to the Python import implementation: https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 When we import a module we should also import its parents, and set the child as attribute of its parent.
1 parent 31ffd94 commit 815f4e1

File tree

2 files changed

+49
-15
lines changed

2 files changed

+49
-15
lines changed

src/_pytest/pathlib.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -636,31 +636,30 @@ def _import_module_using_spec(
636636

637637
if spec_matches_module_path(spec, module_path):
638638
assert spec is not None
639+
# Attempt to import the parent module, seems is our responsibility:
640+
# https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
641+
parent_module_name, _, name = module_name.rpartition(".")
642+
parent_module: Optional[ModuleType] = sys.modules.get(parent_module_name)
643+
if parent_module is None and parent_module_name:
644+
with contextlib.suppress(ModuleNotFoundError, ImportWarning):
645+
parent_module = importlib.import_module(parent_module_name)
646+
647+
# Find spec and import this module.
639648
mod = importlib.util.module_from_spec(spec)
640649
sys.modules[module_name] = mod
641650
spec.loader.exec_module(mod) # type: ignore[union-attr]
651+
652+
# Set this module as an attribute of the parent module (#12194).
653+
if parent_module is not None:
654+
setattr(parent_module, name, mod)
655+
642656
if insert_modules:
643657
insert_missing_modules(sys.modules, module_name)
644-
_set_name_in_parent(mod)
645658
return mod
646659

647660
return None
648661

649662

650-
def _set_name_in_parent(module: ModuleType) -> None:
651-
"""
652-
Sets an attribute in the module's parent pointing to the module itself (#12194).
653-
654-
Based on https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1335-L1342.
655-
"""
656-
parent, _, name = module.__name__.rpartition(".")
657-
if not parent:
658-
return
659-
parent_module = sys.modules.get(parent)
660-
if parent_module is not None:
661-
setattr(sys.modules[parent], name, module)
662-
663-
664663
def spec_matches_module_path(
665664
module_spec: Optional[ModuleSpec], module_path: Path
666665
) -> bool:

testing/test_pathlib.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,41 @@ def test_safe_exists(tmp_path: Path) -> None:
11271127

11281128

11291129
def test_import_sets_module_as_attribute(pytester: Pytester) -> None:
1130+
"""Unittest test for #12194."""
1131+
pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
1132+
pytester.path.joinpath("foo/__init__.py").touch()
1133+
pytester.path.joinpath("foo/bar/__init__.py").touch()
1134+
pytester.path.joinpath("foo/bar/baz/__init__.py").touch()
1135+
pytester.syspathinsert()
1136+
1137+
# Import foo.bar.baz and ensure parent modules also ended up imported.
1138+
baz = import_path(
1139+
pytester.path.joinpath("foo/bar/baz/__init__.py"),
1140+
mode=ImportMode.importlib,
1141+
root=pytester.path,
1142+
consider_namespace_packages=False,
1143+
)
1144+
assert baz.__name__ == "foo.bar.baz"
1145+
foo = sys.modules["foo"]
1146+
assert foo.__name__ == "foo"
1147+
bar = sys.modules["foo.bar"]
1148+
assert bar.__name__ == "foo.bar"
1149+
1150+
# Check parent modules have an attribute pointing to their children.
1151+
assert bar.baz is baz
1152+
assert foo.bar is bar
1153+
1154+
# Ensure we returned the "foo.bar" module cached in sys.modules.
1155+
bar_2 = import_path(
1156+
pytester.path.joinpath("foo/bar/__init__.py"),
1157+
mode=ImportMode.importlib,
1158+
root=pytester.path,
1159+
consider_namespace_packages=False,
1160+
)
1161+
assert bar_2 is bar
1162+
1163+
1164+
def test_import_sets_module_as_attribute_regression(pytester: Pytester) -> None:
11301165
"""Regression test for #12194."""
11311166
pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
11321167
pytester.path.joinpath("foo/__init__.py").touch()

0 commit comments

Comments
 (0)