From 111c0d910e15afb9e1ed1f6862e1637594eaa076 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 2 Mar 2024 11:46:54 -0300 Subject: [PATCH] Add consider_namespace_packages ini option Fix #11475 --- src/_pytest/config/__init__.py | 68 ++++++++++-- src/_pytest/main.py | 6 ++ src/_pytest/pathlib.py | 16 +-- src/_pytest/python.py | 7 +- src/_pytest/runner.py | 3 + testing/code/test_excinfo.py | 6 +- testing/code/test_source.py | 2 +- testing/test_conftest.py | 76 +++++++++++--- testing/test_pathlib.py | 182 +++++++++++++++++++++++++-------- testing/test_pluginmanager.py | 26 ++++- 10 files changed, 315 insertions(+), 77 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 069e2196d25..7ed79483c4e 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -547,6 +547,8 @@ def _set_initial_conftests( confcutdir: Optional[Path], invocation_dir: Path, importmode: Union[ImportMode, str], + *, + consider_namespace_packages: bool, ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -572,10 +574,20 @@ def _set_initial_conftests( # Ensure we do not break if what appears to be an anchor # is in fact a very long option (#10169, #11394). if safe_exists(anchor): - self._try_load_conftest(anchor, importmode, rootpath) + self._try_load_conftest( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) foundanchor = True if not foundanchor: - self._try_load_conftest(invocation_dir, importmode, rootpath) + self._try_load_conftest( + invocation_dir, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) def _is_in_confcutdir(self, path: Path) -> bool: """Whether to consider the given path to load conftests from.""" @@ -593,17 +605,37 @@ def _is_in_confcutdir(self, path: Path) -> bool: return path not in self._confcutdir.parents def _try_load_conftest( - self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + anchor: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, ) -> None: - self._loadconftestmodules(anchor, importmode, rootpath) + self._loadconftestmodules( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) # let's also consider test* subdirs if anchor.is_dir(): for x in anchor.glob("test*"): if x.is_dir(): - self._loadconftestmodules(x, importmode, rootpath) + self._loadconftestmodules( + x, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) def _loadconftestmodules( - self, path: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + path: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, ) -> None: if self._noconftest: return @@ -620,7 +652,12 @@ def _loadconftestmodules( if self._is_in_confcutdir(parent): conftestpath = parent / "conftest.py" if conftestpath.is_file(): - mod = self._importconftest(conftestpath, importmode, rootpath) + mod = self._importconftest( + conftestpath, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) clist.append(mod) self._dirpath2confmods[directory] = clist @@ -642,7 +679,12 @@ def _rget_with_confmod( raise KeyError(name) def _importconftest( - self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path + self, + conftestpath: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + *, + consider_namespace_packages: bool, ) -> types.ModuleType: conftestpath_plugin_name = str(conftestpath) existing = self.get_plugin(conftestpath_plugin_name) @@ -661,7 +703,12 @@ def _importconftest( pass try: - mod = import_path(conftestpath, mode=importmode, root=rootpath) + mod = import_path( + conftestpath, + mode=importmode, + root=rootpath, + consider_namespace_packages=consider_namespace_packages, + ) except Exception as e: assert e.__traceback__ is not None raise ConftestImportFailure(conftestpath, cause=e) from e @@ -1177,6 +1224,9 @@ def pytest_load_initial_conftests(self, early_config: "Config") -> None: confcutdir=early_config.known_args_namespace.confcutdir, invocation_dir=early_config.invocation_params.dir, importmode=early_config.known_args_namespace.importmode, + consider_namespace_packages=early_config.getini( + "consider_namespace_packages" + ), ) def _initini(self, args: Sequence[str]) -> None: diff --git a/src/_pytest/main.py b/src/_pytest/main.py index b7ed72ddc3b..8e8d238acbb 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -222,6 +222,12 @@ def pytest_addoption(parser: Parser) -> None: help="Prepend/append to sys.path when importing test modules and conftest " "files. Default: prepend.", ) + parser.addini( + "consider_namespace_packages", + type="bool", + default=False, + help="Consider namespace packages when resolving module names during import", + ) group = parser.getgroup("debugconfig", "test session debugging and configuration") group.addoption( diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b4a0ceb2277..a19e89aa116 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -488,6 +488,7 @@ def import_path( *, mode: Union[str, ImportMode] = ImportMode.prepend, root: Path, + consider_namespace_packages: bool, ) -> ModuleType: """ Import and return a module from the given path, which can be a file (a module) or @@ -515,6 +516,9 @@ def import_path( a unique name for the module being imported so it can safely be stored into ``sys.modules``. + :param consider_namespace_packages: + If True, consider namespace packages when resolving module names. + :raises ImportPathMismatchError: If after importing the given `path` and the module `__file__` are different. Only raised in `prepend` and `append` modes. @@ -530,7 +534,7 @@ def import_path( # without touching sys.path. try: pkg_root, module_name = resolve_pkg_root_and_module_name( - path, consider_ns_packages=True + path, consider_namespace_packages=consider_namespace_packages ) except CouldNotResolvePathError: pass @@ -556,7 +560,7 @@ def import_path( try: pkg_root, module_name = resolve_pkg_root_and_module_name( - path, consider_ns_packages=True + path, consider_namespace_packages=consider_namespace_packages ) except CouldNotResolvePathError: pkg_root, module_name = path.parent, path.stem @@ -674,7 +678,7 @@ def module_name_from_path(path: Path, root: Path) -> str: # Module names cannot contain ".", normalize them to "_". This prevents # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules. # Also, important to replace "." at the start of paths, as those are considered relative imports. - path_parts = [x.replace(".", "_") for x in path_parts] + path_parts = tuple(x.replace(".", "_") for x in path_parts) return ".".join(path_parts) @@ -738,7 +742,7 @@ def resolve_package_path(path: Path) -> Optional[Path]: def resolve_pkg_root_and_module_name( - path: Path, *, consider_ns_packages: bool = False + path: Path, *, consider_namespace_packages: bool = False ) -> Tuple[Path, str]: """ Return the path to the directory of the root package that contains the @@ -753,7 +757,7 @@ def resolve_pkg_root_and_module_name( Passing the full path to `models.py` will yield Path("src") and "app.core.models". - If consider_ns_packages is True, then we additionally check upwards in the hierarchy + If consider_namespace_packages is True, then we additionally check upwards in the hierarchy until we find a directory that is reachable from sys.path, which marks it as a namespace package: https://packaging.python.org/en/latest/guides/packaging-namespace-packages @@ -764,7 +768,7 @@ def resolve_pkg_root_and_module_name( if pkg_path is not None: pkg_root = pkg_path.parent # https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ - if consider_ns_packages: + if consider_namespace_packages: # Go upwards in the hierarchy, if we find a parent path included # in sys.path, it means the package found by resolve_package_path() # actually belongs to a namespace package. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ca64a877d42..e1730b1a7e0 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -516,7 +516,12 @@ def importtestmodule( # We assume we are only called once per module. importmode = config.getoption("--import-mode") try: - mod = import_path(path, mode=importmode, root=config.rootpath) + mod = import_path( + path, + mode=importmode, + root=config.rootpath, + consider_namespace_packages=config.getini("consider_namespace_packages"), + ) except SyntaxError as e: raise nodes.Collector.CollectError( ExceptionInfo.from_current().getrepr(style="short") diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index b60af9dd3fb..16abb895d58 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -380,6 +380,9 @@ def collect() -> List[Union[Item, Collector]]: collector.path, collector.config.getoption("importmode"), rootpath=collector.config.rootpath, + consider_namespace_packages=collector.config.getini( + "consider_namespace_packages" + ), ) return list(collector.collect()) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index cce23bf87d4..49c5dd3715b 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -180,7 +180,7 @@ def test_traceback_cut(self) -> None: def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: p = pytester.makepyfile("def f(): raise ValueError") with pytest.raises(ValueError) as excinfo: - import_path(p, root=pytester.path).f() # type: ignore[attr-defined] + import_path(p, root=pytester.path, consider_namespace_packages=False).f() # type: ignore[attr-defined] basedir = Path(pytest.__file__).parent newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: @@ -543,7 +543,9 @@ def importasmod(source): tmp_path.joinpath("__init__.py").touch() modpath.write_text(source, encoding="utf-8") importlib.invalidate_caches() - return import_path(modpath, root=tmp_path) + return import_path( + modpath, root=tmp_path, consider_namespace_packages=False + ) return importasmod diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 9d0565380e4..12ea27b3517 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -296,7 +296,7 @@ def method(self): ) path = tmp_path.joinpath("a.py") path.write_text(str(source), encoding="utf-8") - mod: Any = import_path(path, root=tmp_path) + mod: Any = import_path(path, root=tmp_path, consider_namespace_packages=False) s2 = Source(mod.A) assert str(source).strip() == str(s2).strip() diff --git a/testing/test_conftest.py b/testing/test_conftest.py index bb74fa75d61..3116dfe2584 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -38,6 +38,7 @@ def conftest_setinitial( confcutdir=confcutdir, invocation_dir=Path.cwd(), importmode="prepend", + consider_namespace_packages=False, ) @@ -64,7 +65,9 @@ def basedir( def test_basic_init(self, basedir: Path) -> None: conftest = PytestPluginManager() p = basedir / "adir" - conftest._loadconftestmodules(p, importmode="prepend", rootpath=basedir) + conftest._loadconftestmodules( + p, importmode="prepend", rootpath=basedir, consider_namespace_packages=False + ) assert conftest._rget_with_confmod("a", p)[1] == 1 def test_immediate_initialiation_and_incremental_are_the_same( @@ -72,15 +75,26 @@ def test_immediate_initialiation_and_incremental_are_the_same( ) -> None: conftest = PytestPluginManager() assert not len(conftest._dirpath2confmods) - conftest._loadconftestmodules(basedir, importmode="prepend", rootpath=basedir) + conftest._loadconftestmodules( + basedir, + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, + ) snap1 = len(conftest._dirpath2confmods) assert snap1 == 1 conftest._loadconftestmodules( - basedir / "adir", importmode="prepend", rootpath=basedir + basedir / "adir", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) assert len(conftest._dirpath2confmods) == snap1 + 1 conftest._loadconftestmodules( - basedir / "b", importmode="prepend", rootpath=basedir + basedir / "b", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) assert len(conftest._dirpath2confmods) == snap1 + 2 @@ -92,10 +106,18 @@ def test_value_access_not_existing(self, basedir: Path) -> None: def test_value_access_by_path(self, basedir: Path) -> None: conftest = ConftestWithSetinitial(basedir) adir = basedir / "adir" - conftest._loadconftestmodules(adir, importmode="prepend", rootpath=basedir) + conftest._loadconftestmodules( + adir, + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, + ) assert conftest._rget_with_confmod("a", adir)[1] == 1 conftest._loadconftestmodules( - adir / "b", importmode="prepend", rootpath=basedir + adir / "b", + importmode="prepend", + rootpath=basedir, + consider_namespace_packages=False, ) assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5 @@ -152,7 +174,12 @@ def test_conftest_global_import(pytester: Pytester) -> None: import pytest from _pytest.config import PytestPluginManager conf = PytestPluginManager() - mod = conf._importconftest(Path("conftest.py"), importmode="prepend", rootpath=Path.cwd()) + mod = conf._importconftest( + Path("conftest.py"), + importmode="prepend", + rootpath=Path.cwd(), + consider_namespace_packages=False, + ) assert mod.x == 3 import conftest assert conftest is mod, (conftest, mod) @@ -160,7 +187,12 @@ def test_conftest_global_import(pytester: Pytester) -> None: sub.mkdir() subconf = sub / "conftest.py" subconf.write_text("y=4", encoding="utf-8") - mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd()) + mod2 = conf._importconftest( + subconf, + importmode="prepend", + rootpath=Path.cwd(), + consider_namespace_packages=False, + ) assert mod != mod2 assert mod2.y == 4 import conftest @@ -176,17 +208,30 @@ def test_conftestcutdir(pytester: Pytester) -> None: p = pytester.mkdir("x") conftest = PytestPluginManager() conftest_setinitial(conftest, [pytester.path], confcutdir=p) - conftest._loadconftestmodules(p, importmode="prepend", rootpath=pytester.path) + conftest._loadconftestmodules( + p, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, + ) values = conftest._getconftestmodules(p) assert len(values) == 0 conftest._loadconftestmodules( - conf.parent, importmode="prepend", rootpath=pytester.path + conf.parent, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) values = conftest._getconftestmodules(conf.parent) assert len(values) == 0 assert not conftest.has_plugin(str(conf)) # but we can still import a conftest directly - conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path) + conftest._importconftest( + conf, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, + ) values = conftest._getconftestmodules(conf.parent) assert values[0].__file__ is not None assert values[0].__file__.startswith(str(conf)) @@ -405,13 +450,18 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> ct2 = sub / "conftest.py" ct2.write_text("", encoding="utf-8") - def impct(p, importmode, root): + def impct(p, importmode, root, consider_namespace_packages): return p conftest = PytestPluginManager() conftest._confcutdir = pytester.path monkeypatch.setattr(conftest, "_importconftest", impct) - conftest._loadconftestmodules(sub, importmode="prepend", rootpath=pytester.path) + conftest._loadconftestmodules( + sub, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, + ) mods = cast(List[Path], conftest._getconftestmodules(sub)) expected = [ct1, ct2] assert mods == expected diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 13c22b8007a..a5d582bc4bd 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -171,13 +171,17 @@ def setuptestfs(self, path: Path) -> None: ) def test_smoke_test(self, path1: Path) -> None: - obj = import_path(path1 / "execfile.py", root=path1) + obj = import_path( + path1 / "execfile.py", root=path1, consider_namespace_packages=False + ) assert obj.x == 42 # type: ignore[attr-defined] assert obj.__name__ == "execfile" def test_import_path_missing_file(self, path1: Path) -> None: with pytest.raises(ImportPathMismatchError): - import_path(path1 / "sampledir", root=path1) + import_path( + path1 / "sampledir", root=path1, consider_namespace_packages=False + ) def test_renamed_dir_creates_mismatch( self, tmp_path: Path, monkeypatch: MonkeyPatch @@ -185,25 +189,37 @@ def test_renamed_dir_creates_mismatch( tmp_path.joinpath("a").mkdir() p = tmp_path.joinpath("a", "test_x123.py") p.touch() - import_path(p, root=tmp_path) + import_path(p, root=tmp_path, consider_namespace_packages=False) tmp_path.joinpath("a").rename(tmp_path.joinpath("b")) with pytest.raises(ImportPathMismatchError): - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=False, + ) # Errors can be ignored. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=False, + ) # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") with pytest.raises(ImportPathMismatchError): - import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path) + import_path( + tmp_path.joinpath("b", "test_x123.py"), + root=tmp_path, + consider_namespace_packages=False, + ) def test_messy_name(self, tmp_path: Path) -> None: # https://bitbucket.org/hpk42/py-trunk/issue/129 path = tmp_path / "foo__init__.py" path.touch() - module = import_path(path, root=tmp_path) + module = import_path(path, root=tmp_path, consider_namespace_packages=False) assert module.__name__ == "foo__init__" def test_dir(self, tmp_path: Path) -> None: @@ -211,31 +227,39 @@ def test_dir(self, tmp_path: Path) -> None: p.mkdir() p_init = p / "__init__.py" p_init.touch() - m = import_path(p, root=tmp_path) + m = import_path(p, root=tmp_path, consider_namespace_packages=False) assert m.__name__ == "hello_123" - m = import_path(p_init, root=tmp_path) + m = import_path(p_init, root=tmp_path, consider_namespace_packages=False) assert m.__name__ == "hello_123" def test_a(self, path1: Path) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "a.py", root=path1) + mod = import_path( + otherdir / "a.py", root=path1, consider_namespace_packages=False + ) assert mod.result == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.a" def test_b(self, path1: Path) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "b.py", root=path1) + mod = import_path( + otherdir / "b.py", root=path1, consider_namespace_packages=False + ) assert mod.stuff == "got it" # type: ignore[attr-defined] assert mod.__name__ == "otherdir.b" def test_c(self, path1: Path) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "c.py", root=path1) + mod = import_path( + otherdir / "c.py", root=path1, consider_namespace_packages=False + ) assert mod.value == "got it" # type: ignore[attr-defined] def test_d(self, path1: Path) -> None: otherdir = path1 / "otherdir" - mod = import_path(otherdir / "d.py", root=path1) + mod = import_path( + otherdir / "d.py", root=path1, consider_namespace_packages=False + ) assert mod.value2 == "got it" # type: ignore[attr-defined] def test_import_after(self, tmp_path: Path) -> None: @@ -243,7 +267,7 @@ def test_import_after(self, tmp_path: Path) -> None: tmp_path.joinpath("xxxpackage", "__init__.py").touch() mod1path = tmp_path.joinpath("xxxpackage", "module1.py") mod1path.touch() - mod1 = import_path(mod1path, root=tmp_path) + mod1 = import_path(mod1path, root=tmp_path, consider_namespace_packages=False) assert mod1.__name__ == "xxxpackage.module1" from xxxpackage import module1 @@ -262,7 +286,9 @@ def test_check_filepath_consistency( pseudopath.touch() mod.__file__ = str(pseudopath) mp.setitem(sys.modules, name, mod) - newmod = import_path(p, root=tmp_path) + newmod = import_path( + p, root=tmp_path, consider_namespace_packages=False + ) assert mod == newmod mod = ModuleType(name) pseudopath = tmp_path.joinpath(name + "123.py") @@ -270,7 +296,7 @@ def test_check_filepath_consistency( mod.__file__ = str(pseudopath) monkeypatch.setitem(sys.modules, name, mod) with pytest.raises(ImportPathMismatchError) as excinfo: - import_path(p, root=tmp_path) + import_path(p, root=tmp_path, consider_namespace_packages=False) modname, modfile, orig = excinfo.value.args assert modname == name assert modfile == str(pseudopath) @@ -283,13 +309,19 @@ def test_ensuresyspath_append(self, tmp_path: Path) -> None: file1 = root1 / "x123.py" file1.touch() assert str(root1) not in sys.path - import_path(file1, mode="append", root=tmp_path) + import_path( + file1, mode="append", root=tmp_path, consider_namespace_packages=False + ) assert str(root1) == sys.path[-1] assert str(root1) not in sys.path[:-1] def test_invalid_path(self, tmp_path: Path) -> None: with pytest.raises(ImportError): - import_path(tmp_path / "invalid.py", root=tmp_path) + import_path( + tmp_path / "invalid.py", + root=tmp_path, + consider_namespace_packages=False, + ) @pytest.fixture def simple_module( @@ -307,7 +339,12 @@ def test_importmode_importlib( self, simple_module: Path, tmp_path: Path, request: pytest.FixtureRequest ) -> None: """`importlib` mode does not change sys.path.""" - module = import_path(simple_module, mode="importlib", root=tmp_path) + module = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) assert module.foo(2) == 42 # type: ignore[attr-defined] assert str(simple_module.parent) not in sys.path assert module.__name__ in sys.modules @@ -319,8 +356,18 @@ def test_remembers_previous_imports( self, simple_module: Path, tmp_path: Path ) -> None: """`importlib` mode called remembers previous module (#10341, #10811).""" - module1 = import_path(simple_module, mode="importlib", root=tmp_path) - module2 = import_path(simple_module, mode="importlib", root=tmp_path) + module1 = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) + module2 = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) assert module1 is module2 def test_no_meta_path_found( @@ -328,7 +375,12 @@ def test_no_meta_path_found( ) -> None: """Even without any meta_path should still import module.""" monkeypatch.setattr(sys, "meta_path", []) - module = import_path(simple_module, mode="importlib", root=tmp_path) + module = import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) assert module.foo(2) == 42 # type: ignore[attr-defined] # mode='importlib' fails if no spec is found to load the module @@ -341,7 +393,12 @@ def test_no_meta_path_found( importlib.util, "spec_from_file_location", lambda *args: None ) with pytest.raises(ImportError): - import_path(simple_module, mode="importlib", root=tmp_path) + import_path( + simple_module, + mode="importlib", + root=tmp_path, + consider_namespace_packages=False, + ) def test_resolve_package_path(tmp_path: Path) -> None: @@ -477,7 +534,9 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N # the paths too. Using a context to narrow the patch as much as possible given # this is an important system function. mp.setattr(os.path, "samefile", lambda x, y: False) - module = import_path(module_path, root=tmp_path) + module = import_path( + module_path, root=tmp_path, consider_namespace_packages=False + ) assert getattr(module, "foo")() == 42 @@ -499,7 +558,9 @@ class Data: encoding="utf-8", ) - module = import_path(fn, mode="importlib", root=tmp_path) + module = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=False + ) Data: Any = getattr(module, "Data") data = Data(value="foo") assert data.value == "foo" @@ -525,7 +586,9 @@ def round_trip(): encoding="utf-8", ) - module = import_path(fn, mode="importlib", root=tmp_path) + module = import_path( + fn, mode="importlib", root=tmp_path, consider_namespace_packages=False + ) round_trip = getattr(module, "round_trip") action = round_trip() assert action() == 42 @@ -575,10 +638,14 @@ def round_trip(obj): s = pickle.dumps(obj) return pickle.loads(s) - module = import_path(fn1, mode="importlib", root=tmp_path) + module = import_path( + fn1, mode="importlib", root=tmp_path, consider_namespace_packages=False + ) Data1 = getattr(module, "Data") - module = import_path(fn2, mode="importlib", root=tmp_path) + module = import_path( + fn2, mode="importlib", root=tmp_path, consider_namespace_packages=False + ) Data2 = getattr(module, "Data") assert round_trip(Data1(20)) == Data1(20) @@ -635,7 +702,7 @@ def test_resolve_pkg_root_and_module_name( # If we add tmp_path to sys.path, src becomes a namespace package. monkeypatch.syspath_prepend(tmp_path) assert resolve_pkg_root_and_module_name( - models_py, consider_ns_packages=True + models_py, consider_namespace_packages=True ) == ( tmp_path, "src.app.core.models", @@ -709,7 +776,12 @@ def __init__(self) -> None: encoding="ascii", ) - mod = import_path(init, root=tmp_path, mode=ImportMode.importlib) + mod = import_path( + init, + root=tmp_path, + mode=ImportMode.importlib, + consider_namespace_packages=False, + ) assert len(mod.instance.INSTANCES) == 1 def test_importlib_root_is_package(self, pytester: Pytester) -> None: @@ -828,16 +900,31 @@ def test_import_using_normal_mechanism_first( ) # core_py is reached from sys.path, so should be imported normally. - mod = import_path(core_py, mode="importlib", root=pytester.path) + mod = import_path( + core_py, + mode="importlib", + root=pytester.path, + consider_namespace_packages=False, + ) assert mod.__name__ == "app.core" assert mod.__file__ and Path(mod.__file__) == core_py # tests are not reachable from sys.path, so they are imported as a standalone modules. # Instead of '.tests.a.test_core', we import as "_tests.a.test_core" because # importlib considers module names starting with '.' to be local imports. - mod = import_path(test_path1, mode="importlib", root=pytester.path) + mod = import_path( + test_path1, + mode="importlib", + root=pytester.path, + consider_namespace_packages=False, + ) assert mod.__name__ == "_tests.a.test_core" - mod = import_path(test_path2, mode="importlib", root=pytester.path) + mod = import_path( + test_path2, + mode="importlib", + root=pytester.path, + consider_namespace_packages=False, + ) assert mod.__name__ == "_tests.b.test_core" def test_import_using_normal_mechanism_first_integration( @@ -889,14 +976,22 @@ def test_import_path_imports_correct_file(self, pytester: Pytester) -> None: # The 'x.py' module from sys.path was not imported for sure because # otherwise we would get an AssertionError. mod = import_path( - x_in_sub_folder, mode=ImportMode.importlib, root=pytester.path + x_in_sub_folder, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, ) assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder assert mod.X == "a/b/x" # Attempt to import root 'x.py'. with pytest.raises(AssertionError, match="x at root"): - _ = import_path(x_at_root, mode=ImportMode.importlib, root=pytester.path) + _ = import_path( + x_at_root, + mode=ImportMode.importlib, + root=pytester.path, + consider_namespace_packages=False, + ) def test_safe_exists(tmp_path: Path) -> None: @@ -979,26 +1074,33 @@ def test_resolve_pkg_root_and_module_name_ns_multiple_levels( ) pkg_root, module_name = resolve_pkg_root_and_module_name( - models_py, consider_ns_packages=True + models_py, consider_namespace_packages=True ) assert (pkg_root, module_name) == ( tmp_path / "src/dist1", "com.company.app.core.models", ) - mod = import_path(models_py, mode=import_mode, root=tmp_path) + mod = import_path( + models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True + ) assert mod.__name__ == "com.company.app.core.models" assert mod.__file__ == str(models_py) pkg_root, module_name = resolve_pkg_root_and_module_name( - algorithms_py, consider_ns_packages=True + algorithms_py, consider_namespace_packages=True ) assert (pkg_root, module_name) == ( tmp_path / "src/dist2", "com.company.calc.algo.algorithms", ) - mod = import_path(algorithms_py, mode=import_mode, root=tmp_path) + mod = import_path( + algorithms_py, + mode=import_mode, + root=tmp_path, + consider_namespace_packages=True, + ) assert mod.__name__ == "com.company.calc.algo.algorithms" assert mod.__file__ == str(algorithms_py) @@ -1019,7 +1121,7 @@ def test_incorrect_namespace_package( (tmp_path / "src/dist1/com/__init__.py").touch() pkg_root, module_name = resolve_pkg_root_and_module_name( - models_py, consider_ns_packages=True + models_py, consider_namespace_packages=True ) assert (pkg_root, module_name) == ( tmp_path / "src/dist1/com/company", diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index f68f143f433..da43364f643 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -46,7 +46,10 @@ def pytest_myhook(xyz): kwargs=dict(pluginmanager=config.pluginmanager) ) config.pluginmanager._importconftest( - conf, importmode="prepend", rootpath=pytester.path + conf, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) # print(config.pluginmanager.get_plugins()) res = config.hook.pytest_myhook(xyz=10) @@ -75,7 +78,10 @@ def pytest_addoption(parser): """ ) config.pluginmanager._importconftest( - p, importmode="prepend", rootpath=pytester.path + p, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) assert config.option.test123 @@ -115,6 +121,7 @@ def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None: conftest, importmode="prepend", rootpath=pytester.path, + consider_namespace_packages=False, ) plugin = config.pluginmanager.get_plugin(str(conftest)) assert plugin is mod @@ -123,6 +130,7 @@ def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None: conftest_upper_case, importmode="prepend", rootpath=pytester.path, + consider_namespace_packages=False, ) plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case)) assert plugin_uppercase is mod_uppercase @@ -174,12 +182,18 @@ def test_hook_proxy(self, pytester: Pytester) -> None: conftest2 = pytester.path.joinpath("tests/subdir/conftest.py") config.pluginmanager._importconftest( - conftest1, importmode="prepend", rootpath=pytester.path + conftest1, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) ihook_a = session.gethookproxy(pytester.path / "tests") assert ihook_a is not None config.pluginmanager._importconftest( - conftest2, importmode="prepend", rootpath=pytester.path + conftest2, + importmode="prepend", + rootpath=pytester.path, + consider_namespace_packages=False, ) ihook_b = session.gethookproxy(pytester.path / "tests") assert ihook_a is not ihook_b @@ -398,7 +412,9 @@ def test_consider_conftest_deps( pytestpm: PytestPluginManager, ) -> None: mod = import_path( - pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path + pytester.makepyfile("pytest_plugins='xyz'"), + root=pytester.path, + consider_namespace_packages=False, ) with pytest.raises(ImportError): pytestpm.consider_conftest(mod, registration_name="unused")