diff --git a/changelog/12194.bugfix.rst b/changelog/12194.bugfix.rst new file mode 100644 index 000000000..6983ba35a --- /dev/null +++ b/changelog/12194.bugfix.rst @@ -0,0 +1 @@ +Fixed a bug with ``--importmode=importlib`` and ``--doctest-modules`` where child modules did not appear as attributes in parent modules. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 254d9d946..3cdaa0b6e 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -641,11 +641,26 @@ def _import_module_using_spec( spec.loader.exec_module(mod) # type: ignore[union-attr] if insert_modules: insert_missing_modules(sys.modules, module_name) + _set_name_in_parent(mod) return mod return None +def _set_name_in_parent(module: ModuleType) -> None: + """ + Sets an attribute in the module's parent pointing to the module itself (#12194). + + Based on https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1335-L1342. + """ + parent, _, name = module.__name__.rpartition(".") + if not parent: + return + parent_module = sys.modules.get(parent) + if parent_module is not None: + setattr(sys.modules[parent], name, module) + + def spec_matches_module_path( module_spec: Optional[ModuleSpec], module_path: Path ) -> bool: diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index f96151bdd..dbd931474 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1126,6 +1126,31 @@ def test_safe_exists(tmp_path: Path) -> None: assert safe_exists(p) is False +def test_import_sets_module_as_attribute(pytester: Pytester) -> None: + """Regression test for #12194.""" + pytester.path.joinpath("foo/bar/baz").mkdir(parents=True) + pytester.path.joinpath("foo/__init__.py").touch() + pytester.path.joinpath("foo/bar/__init__.py").touch() + pytester.path.joinpath("foo/bar/baz/__init__.py").touch() + f = pytester.makepyfile( + """ + import foo + from foo.bar import baz + foo.bar.baz + + def test_foo() -> None: + pass + """ + ) + + pytester.syspathinsert() + result = pytester.runpython(f) + assert result.ret == 0 + + result = pytester.runpytest("--import-mode=importlib", "--doctest-modules") + assert result.ret == 0 + + class TestNamespacePackages: """Test import_path support when importing from properly namespace packages."""