From 474d0b6b3f19107239cff3ad07b21faeae70f496 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Wed, 15 Feb 2023 19:25:20 +0000 Subject: [PATCH 1/3] Capture expectation of 3806 in test --- setuptools/tests/test_editable_install.py | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index ac574c0eb8..c232881526 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -495,6 +495,49 @@ def test_dynamic_path_computation(self, tmp_path): three = import_module("parent.child.three") assert three.x == 3 + @pytest.mark.parametrize("use_namespace", (False, True)) + def test_precedence(self, tmp_path, use_namespace): + """ + Editable installs should take precedence over other entries in ``sys.path`` + See issues #3806, #3828. + """ + files = { + "project1": {"pkg": {"__init__.py": "", "mod.py": "x = 1"}}, + "project2": {"pkg": {"mod.py": "x = 2"}}, + } + if use_namespace: + namespaces = {"pkg": [str(tmp_path / "project2/pkg")]} + else: + files["project2"]["pkg"]["__init__.py"] = "" + namespaces = {} + + jaraco.path.build(files, prefix=tmp_path) + + mapping = {"pkg": str(tmp_path / "project2/pkg")} + template = _finder_template(str(uuid4()), mapping, namespaces) + + with contexts.save_paths(), contexts.save_sys_modules(): + for mod in ("pkg", "pkg.mod"): + sys.modules.pop(mod, None) + + # Simulate a sys.path entry with low precedence + sys.path.append(str(tmp_path / "project1")) + + self.install_finder(template) + mod = import_module("pkg.mod") + + # Editable install should take precedence + assert mod.x == 2 + + try: + expected = str((tmp_path / "project2/pkg").resolve()) + assert_path(import_module("pkg"), expected) + except AssertionError: + if use_namespace: + msg = "Namespace priority is tricky (related: pfmoore/editables#21)" + pytest.xfail(reason=f"TODO: {msg}") + raise + def test_no_recursion(self, tmp_path): # See issue #3550 files = { From dcbf4500e163214b934865eec6ff23bf6b9f75ed Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Wed, 15 Feb 2023 19:25:37 +0000 Subject: [PATCH 2/3] Fix priority of editabble installation --- setuptools/command/editable_wheel.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 6fddf03d61..4163b0c25b 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -731,11 +731,12 @@ def _get_root(self): _FINDER_TEMPLATE = """\ import sys -from importlib.machinery import ModuleSpec +from importlib.machinery import ModuleSpec, PathFinder from importlib.machinery import all_suffixes as module_suffixes from importlib.util import spec_from_file_location from itertools import chain from pathlib import Path +from zipimport import zipimporter MAPPING = {mapping!r} NAMESPACES = {namespaces!r} @@ -786,18 +787,24 @@ def find_module(cls, fullname): return None +def _insert_before(alist, reference, new_item): + j = next((i for i, x in enumerate(alist) if x == reference), len(alist)) + alist.insert(j, new_item) + + def install(): if not any(finder == _EditableFinder for finder in sys.meta_path): - sys.meta_path.append(_EditableFinder) + # Avoid inserting at 0 to prevent conflicts with other finders, e.g. pip's + _insert_before(sys.meta_path, PathFinder, _EditableFinder) if not NAMESPACES: return if not any(hook == _EditableNamespaceFinder._path_hook for hook in sys.path_hooks): # PathEntryFinder is needed to create NamespaceSpec without private APIS - sys.path_hooks.append(_EditableNamespaceFinder._path_hook) + _insert_before(sys.path_hooks, zipimporter, _EditableNamespaceFinder._path_hook) if PATH_PLACEHOLDER not in sys.path: - sys.path.append(PATH_PLACEHOLDER) # Used just to trigger the path hook + sys.path.insert(0, PATH_PLACEHOLDER) # Used just to trigger the path hook """ From 96b78db5d601d3a42108e870fa11cf46d44c1f38 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Wed, 15 Feb 2023 19:40:46 +0000 Subject: [PATCH 3/3] Mention limitation in editable docs --- docs/userguide/development_mode.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/userguide/development_mode.rst b/docs/userguide/development_mode.rst index 12d50fbc93..6a4b04a7ad 100644 --- a/docs/userguide/development_mode.rst +++ b/docs/userguide/development_mode.rst @@ -159,6 +159,11 @@ Limitations whose names coincidentally match installed packages may take precedence in :doc:`Python's import system `. Users are encouraged to avoid such scenarios [#cwd]_. +- Setuptools will try to give the right precedence to modules in an editable install. + However this is not always an easy task. If you have a particular order in + ``sys.path`` or some specific import precedence that needs to be respected, + the editable installation as supported by Setuptools might not be able to + fulfil this requirement, and therefore it might not be the right tool for your use case. .. attention:: Editable installs are **not a perfect replacement for regular installs**