summaryrefslogtreecommitdiff
path: root/setuptools
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2022-08-12 15:05:20 +0100
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2022-08-12 15:05:20 +0100
commit0adb305c8b6b1502fa96b595fd3887e9877fa9f7 (patch)
tree06473f5e9526f7804dd46229dcb54e641e54c2d8 /setuptools
parent8f2cc1f47f795de68a2a5a93e7e59fd91a6346d2 (diff)
parentce980c1fe7d286235a1f1c3751569e931bc7c2be (diff)
downloadpython-setuptools-git-0adb305c8b6b1502fa96b595fd3887e9877fa9f7.tar.gz
Handle accidental virtual namespaces in editable install (#3512)
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/command/editable_wheel.py12
-rw-r--r--setuptools/tests/test_editable_install.py50
2 files changed, 59 insertions, 3 deletions
diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py
index 8a53de65..2631a082 100644
--- a/setuptools/command/editable_wheel.py
+++ b/setuptools/command/editable_wheel.py
@@ -628,7 +628,14 @@ def _absolute_root(path: _Path) -> str:
def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]:
"""By carefully designing ``package_dir``, it is possible to implement the logical
structure of PEP 420 in a package without the corresponding directories.
- This function will try to find this kind of namespaces.
+
+ Moreover a parent package can be purposefully/accidentally skipped in the discovery
+ phase (e.g. ``find_packages(include=["mypkg.*"])``, when ``mypkg.foo`` is included
+ by ``mypkg`` itself is not).
+ We consider this case to also be a virtual namespace (ignoring the original
+ directory) to emulate a non-editable installation.
+
+ This function will try to find these kinds of namespaces.
"""
for pkg in pkg_roots:
if "." not in pkg:
@@ -637,7 +644,8 @@ def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]:
for i in range(len(parts) - 1, 0, -1):
partial_name = ".".join(parts[:i])
path = Path(find_package_path(partial_name, pkg_roots, ""))
- if not path.exists():
+ if not path.exists() or partial_name not in pkg_roots:
+ # partial_name not in pkg_roots ==> purposefully/accidentally skipped
yield partial_name
diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py
index f7612377..67d377ef 100644
--- a/setuptools/tests/test_editable_install.py
+++ b/setuptools/tests/test_editable_install.py
@@ -269,6 +269,54 @@ class TestPep420Namespaces:
venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])
venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"])
+ def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path):
+ """Sometimes users might specify an ``include`` pattern that ignores parent
+ packages. In a normal installation this would ignore all modules inside the
+ parent packages, and make them namespaces (reported in issue #3504),
+ so the editable mode should preserve this behaviour.
+ """
+ files = {
+ "pkgA": {
+ "pyproject.toml": dedent("""\
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "pkgA"
+ version = "3.14159"
+
+ [tool.setuptools]
+ packages.find.include = ["mypkg.*"]
+ """),
+ "mypkg": {
+ "__init__.py": "",
+ "other.py": "b = 1",
+ "n": {
+ "__init__.py": "",
+ "pkgA.py": "a = 1",
+ },
+ },
+ "MANIFEST.in": EXAMPLE["MANIFEST.in"],
+ },
+ }
+ jaraco.path.build(files, prefix=tmp_path)
+ pkg_A = tmp_path / "pkgA"
+
+ # use pip to install to the target directory
+ opts = ["--no-build-isolation"] # force current version of setuptools
+ venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts])
+ out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"])
+ assert str(out, "utf-8").strip() == "1"
+ cmd = """\
+ try:
+ import mypkg.other
+ except ImportError:
+ print("mypkg.other not defined")
+ """
+ out = venv.run(["python", "-c", dedent(cmd)])
+ assert "mypkg.other not defined" in str(out, "utf-8")
+
# Moved here from test_develop:
@pytest.mark.xfail(
@@ -490,7 +538,7 @@ def test_pkg_roots(tmp_path):
assert ns == {"f", "f.g"}
ns = set(_find_virtual_namespaces(roots))
- assert ns == {"a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}
+ assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"}
class TestOverallBehaviour: