From 7a25556ae56e0b8522f6990dce88df028069ba35 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 31 Mar 2024 09:35:48 -0300 Subject: [PATCH] Improve check for __init__ files --- src/_pytest/pathlib.py | 11 +++++------ testing/test_pathlib.py | 21 ++++++++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 70479fe5a..27504230e 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -773,6 +773,11 @@ def resolve_pkg_root_and_module_name( pkg_root = pkg_path.parent if consider_namespace_packages: for candidate in (pkg_root, *pkg_root.parents): + # If any of the parent paths has an __init__.py, it means it is not a namespace package: + # https://packaging.python.org/en/latest/guides/packaging-namespace-packages + if (candidate / "__init__.py").is_file(): + break + if _is_namespace_package(candidate): # Point the pkg_root to the root of the namespace package. pkg_root = candidate.parent @@ -788,12 +793,6 @@ def resolve_pkg_root_and_module_name( def _is_namespace_package(module_path: Path) -> bool: - # If the path has an __init__.py file, it means it is not - # a namespace package: - # https://packaging.python.org/en/latest/guides/packaging-namespace-packages - if (module_path / "__init__.py").is_file(): - return False - module_name = module_path.name # Empty module names break find_spec. diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index d123c18ff..8a162d9ad 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1226,11 +1226,26 @@ class TestNamespacePackages: models_py, algorithms_py = self.setup_directories( tmp_path, monkeypatch, pytester ) - # Namespace packages must not have an __init__.py at any of its - # directories; if it does, we then fall back to importing just the - # part of the package containing the __init__.py files. + # Namespace packages must not have an __init__.py at its top-level + # directory; if it does, it is no longer a namespace package and we fall back + # to importing just the part of the package containing the __init__.py files. (tmp_path / "src/dist1/com/__init__.py").touch() + # Ensure Python no longer considers dist1/com a namespace package. + r = pytester.runpython_c( + dedent( + f""" + import sys + sys.path.append(r{str(tmp_path / "src/dist1")!r}) + sys.path.append(r{str(tmp_path / "src/dist2")!r}) + import com.company.app.core.models + import com.company.calc.algo.algorithms + """ + ) + ) + assert r.ret == 1 + r.stderr.fnmatch_lines("*No module named 'com.company.calc*") + pkg_root, module_name = resolve_pkg_root_and_module_name( models_py, consider_namespace_packages=True )