From 2f7415cfbc4b6ca62f9013f1abd27136f46b9653 Mon Sep 17 00:00:00 2001 From: akhilramkee <31619526+akhilramkee@users.noreply.github.com> Date: Sat, 1 Jul 2023 20:42:41 +0530 Subject: [PATCH] Add child modules as attributes of parent modules. (#10338) Failing to add child modules as attributes of parent module will prevent them from being accessible through parent module. Fix #10337 --- changelog/10337.bugfix.rst | 2 ++ src/_pytest/pathlib.py | 14 +++++++++++++- testing/test_pathlib.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelog/10337.bugfix.rst diff --git a/changelog/10337.bugfix.rst b/changelog/10337.bugfix.rst new file mode 100644 index 000000000..c5eeff19d --- /dev/null +++ b/changelog/10337.bugfix.rst @@ -0,0 +1,2 @@ +Fixed but that fake intermediate modules generated by ``--import-mode=importlib`` would not include the +child modules as attributes of the parent modules. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 70383e4b5..e43310ef0 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -633,6 +633,9 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> otherwise "src.tests.test_foo" is not importable by ``__import__``. """ module_parts = module_name.split(".") + child_module: Union[ModuleType, None] = None + module: Union[ModuleType, None] = None + child_name: str = "" while module_name: if module_name not in modules: try: @@ -642,13 +645,22 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> # ourselves to fall back to creating a dummy module. if not sys.meta_path: raise ModuleNotFoundError - importlib.import_module(module_name) + module = importlib.import_module(module_name) except ModuleNotFoundError: module = ModuleType( module_name, doc="Empty module created by pytest's importmode=importlib.", ) + else: + module = modules[module_name] + if child_module: + # Add child attribute to the parent that can reference the child + # modules. + if not hasattr(module, child_name): + setattr(module, child_name, child_module) modules[module_name] = module + # Keep track of the child module while moving up the tree. + child_module, child_name = module, module_name.rpartition(".")[-1] module_parts.pop(-1) module_name = ".".join(module_parts) diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 56c54e484..c15a81ea1 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -592,3 +592,15 @@ class TestImportLibMode: modules = {} insert_missing_modules(modules, "") assert modules == {} + + def test_parent_contains_child_module_attribute( + self, monkeypatch: MonkeyPatch, tmp_path: Path + ): + monkeypatch.chdir(tmp_path) + # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and + # don't end up being imported. + modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")} + insert_missing_modules(modules, "xxx.tests.foo") + assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"] + assert modules["xxx"].tests is modules["xxx.tests"] + assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]