Fix import_path for packages (#11390)
For packages, `import_path` receives the path to the package's `__init__.py` file, however module names (as they live in `sys.modules`) should not include the `__init__` part. For example, `app/core/__init__.py` should be imported as `app.core`, not as `app.core.__init__`. Fix #11306
This commit is contained in:
parent
8032d21271
commit
194a782e38
|
@ -0,0 +1 @@
|
||||||
|
Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
|
|
@ -623,6 +623,10 @@ def module_name_from_path(path: Path, root: Path) -> str:
|
||||||
# Use the parts for the relative path to the root path.
|
# Use the parts for the relative path to the root path.
|
||||||
path_parts = relative_path.parts
|
path_parts = relative_path.parts
|
||||||
|
|
||||||
|
# Module name for packages do not contain the __init__ file.
|
||||||
|
if path_parts[-1] == "__init__":
|
||||||
|
path_parts = path_parts[:-1]
|
||||||
|
|
||||||
return ".".join(path_parts)
|
return ".".join(path_parts)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ from _pytest.pathlib import fnmatch_ex
|
||||||
from _pytest.pathlib import get_extended_length_path_str
|
from _pytest.pathlib import get_extended_length_path_str
|
||||||
from _pytest.pathlib import get_lock_path
|
from _pytest.pathlib import get_lock_path
|
||||||
from _pytest.pathlib import import_path
|
from _pytest.pathlib import import_path
|
||||||
|
from _pytest.pathlib import ImportMode
|
||||||
from _pytest.pathlib import ImportPathMismatchError
|
from _pytest.pathlib import ImportPathMismatchError
|
||||||
from _pytest.pathlib import insert_missing_modules
|
from _pytest.pathlib import insert_missing_modules
|
||||||
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
from _pytest.pathlib import maybe_delete_a_numbered_dir
|
||||||
|
@ -585,6 +586,10 @@ class TestImportLibMode:
|
||||||
result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
|
result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
|
||||||
assert result == "home.foo.test_foo"
|
assert result == "home.foo.test_foo"
|
||||||
|
|
||||||
|
# Importing __init__.py files should return the package as module name.
|
||||||
|
result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path)
|
||||||
|
assert result == "src.app"
|
||||||
|
|
||||||
def test_insert_missing_modules(
|
def test_insert_missing_modules(
|
||||||
self, monkeypatch: MonkeyPatch, tmp_path: Path
|
self, monkeypatch: MonkeyPatch, tmp_path: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -615,3 +620,43 @@ class TestImportLibMode:
|
||||||
assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
|
assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
|
||||||
assert modules["xxx"].tests is modules["xxx.tests"]
|
assert modules["xxx"].tests is modules["xxx.tests"]
|
||||||
assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
|
assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
|
||||||
|
|
||||||
|
def test_importlib_package(self, monkeypatch: MonkeyPatch, tmp_path: Path):
|
||||||
|
"""
|
||||||
|
Importing a package using --importmode=importlib should not import the
|
||||||
|
package's __init__.py file more than once (#11306).
|
||||||
|
"""
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
monkeypatch.syspath_prepend(tmp_path)
|
||||||
|
|
||||||
|
package_name = "importlib_import_package"
|
||||||
|
tmp_path.joinpath(package_name).mkdir()
|
||||||
|
init = tmp_path.joinpath(f"{package_name}/__init__.py")
|
||||||
|
init.write_text(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
from .singleton import Singleton
|
||||||
|
|
||||||
|
instance = Singleton()
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
encoding="ascii",
|
||||||
|
)
|
||||||
|
singleton = tmp_path.joinpath(f"{package_name}/singleton.py")
|
||||||
|
singleton.write_text(
|
||||||
|
dedent(
|
||||||
|
"""
|
||||||
|
class Singleton:
|
||||||
|
INSTANCES = []
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.INSTANCES.append(self)
|
||||||
|
if len(self.INSTANCES) > 1:
|
||||||
|
raise RuntimeError("Already initialized")
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
encoding="ascii",
|
||||||
|
)
|
||||||
|
|
||||||
|
mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
|
||||||
|
assert len(mod.instance.INSTANCES) == 1
|
||||||
|
|
Loading…
Reference in New Issue