Skip to content

Commit 1de00e9

Browse files
[7.4.x] Fix import_path for packages (#11395)
Co-authored-by: Bruno Oliveira <[email protected]>
1 parent 7f5d9b9 commit 1de00e9

File tree

3 files changed

+50
-0
lines changed

3 files changed

+50
-0
lines changed

changelog/11306.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.

src/_pytest/pathlib.py

+4
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,10 @@ def module_name_from_path(path: Path, root: Path) -> str:
623623
# Use the parts for the relative path to the root path.
624624
path_parts = relative_path.parts
625625

626+
# Module name for packages do not contain the __init__ file.
627+
if path_parts[-1] == "__init__":
628+
path_parts = path_parts[:-1]
629+
626630
return ".".join(path_parts)
627631

628632

testing/test_pathlib.py

+45
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from _pytest.pathlib import get_extended_length_path_str
1919
from _pytest.pathlib import get_lock_path
2020
from _pytest.pathlib import import_path
21+
from _pytest.pathlib import ImportMode
2122
from _pytest.pathlib import ImportPathMismatchError
2223
from _pytest.pathlib import insert_missing_modules
2324
from _pytest.pathlib import maybe_delete_a_numbered_dir
@@ -585,6 +586,10 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
585586
result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
586587
assert result == "home.foo.test_foo"
587588

589+
# Importing __init__.py files should return the package as module name.
590+
result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path)
591+
assert result == "src.app"
592+
588593
def test_insert_missing_modules(
589594
self, monkeypatch: MonkeyPatch, tmp_path: Path
590595
) -> None:
@@ -615,3 +620,43 @@ def test_parent_contains_child_module_attribute(
615620
assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
616621
assert modules["xxx"].tests is modules["xxx.tests"]
617622
assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
623+
624+
def test_importlib_package(self, monkeypatch: MonkeyPatch, tmp_path: Path):
625+
"""
626+
Importing a package using --importmode=importlib should not import the
627+
package's __init__.py file more than once (#11306).
628+
"""
629+
monkeypatch.chdir(tmp_path)
630+
monkeypatch.syspath_prepend(tmp_path)
631+
632+
package_name = "importlib_import_package"
633+
tmp_path.joinpath(package_name).mkdir()
634+
init = tmp_path.joinpath(f"{package_name}/__init__.py")
635+
init.write_text(
636+
dedent(
637+
"""
638+
from .singleton import Singleton
639+
640+
instance = Singleton()
641+
"""
642+
),
643+
encoding="ascii",
644+
)
645+
singleton = tmp_path.joinpath(f"{package_name}/singleton.py")
646+
singleton.write_text(
647+
dedent(
648+
"""
649+
class Singleton:
650+
INSTANCES = []
651+
652+
def __init__(self) -> None:
653+
self.INSTANCES.append(self)
654+
if len(self.INSTANCES) > 1:
655+
raise RuntimeError("Already initialized")
656+
"""
657+
),
658+
encoding="ascii",
659+
)
660+
661+
mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
662+
assert len(mod.instance.INSTANCES) == 1

0 commit comments

Comments
 (0)