Skip to content

Commit

Permalink
[7.4.x] Fix import_path for packages (#11395)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
  • Loading branch information
github-actions[bot] and nicoddemus authored Sep 5, 2023
1 parent 7f5d9b9 commit 1de00e9
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/11306.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
4 changes: 4 additions & 0 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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)


Expand Down
45 changes: 45 additions & 0 deletions testing/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from _pytest.pathlib import get_extended_length_path_str
from _pytest.pathlib import get_lock_path
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportMode
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import insert_missing_modules
from _pytest.pathlib import maybe_delete_a_numbered_dir
Expand Down Expand Up @@ -585,6 +586,10 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
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(
self, monkeypatch: MonkeyPatch, tmp_path: Path
) -> None:
Expand Down Expand Up @@ -615,3 +620,43 @@ def test_parent_contains_child_module_attribute(
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"]

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

0 comments on commit 1de00e9

Please sign in to comment.