Repository: scikit-build/scikit-build-core
Pull request #552 (2023-11-09 19:39:53)

fix: correct editable template & add more tests
I believe this buggy template writing (bug introduced in 0.6.1) actually can't cause a real-world breakage, since the dir is not None if the options after it were set.


Patch:
diff --git a/src/scikit_build_core/build/_editable.py b/src/scikit_build_core/build/_editable.py
index 93f9b25..5cb1ae4 100644
--- a/src/scikit_build_core/build/_editable.py
+++ b/src/scikit_build_core/build/_editable.py
@@ -47,7 +47,7 @@ def editable_redirect(
         build_options,
         install_options,
     )
-    arguments_str = ", ".join(repr(x) for x in arguments if x is not None)
+    arguments_str = ", ".join(repr(x) for x in arguments)
     editable_txt += f"\n\ninstall({arguments_str})\n"
     return editable_txt
 
diff --git a/src/scikit_build_core/resources/_editable_redirect.py b/src/scikit_build_core/resources/_editable_redirect.py
index 2f0c2b9..af75519 100644
--- a/src/scikit_build_core/resources/_editable_redirect.py
+++ b/src/scikit_build_core/resources/_editable_redirect.py
@@ -32,6 +32,7 @@ class ScikitBuildRedirectingFinder(importlib.abc.MetaPathFinder):
         verbose: bool,
         build_options: list[str],
         install_options: list[str],
+        dir: str = DIR,
     ) -> None:
         self.known_source_files = known_source_files
         self.known_wheel_files = known_wheel_files
@@ -40,6 +41,7 @@ class ScikitBuildRedirectingFinder(importlib.abc.MetaPathFinder):
         self.verbose = verbose
         self.build_options = build_options
         self.install_options = install_options
+        self.dir = dir
         # Construct the __path__ of all resource files
         # I.e. the paths of all package-like objects
         submodule_search_locations: dict[str, set[str]] = {}
@@ -65,7 +67,7 @@ class ScikitBuildRedirectingFinder(importlib.abc.MetaPathFinder):
                     msg = f"Unexpected path to source file: {file} [{module}]"
                     raise ImportError(msg)
                 if not os.path.isabs(parent_path):
-                    parent_path = os.path.join(str(DIR), parent_path)
+                    parent_path = os.path.join(self.dir, parent_path)
                 submodule_search_locations[parent].add(parent_path)
         self.submodule_search_locations = submodule_search_locations
         self.pkgs = pkgs
@@ -88,7 +90,7 @@ class ScikitBuildRedirectingFinder(importlib.abc.MetaPathFinder):
                 self.rebuild()
             return importlib.util.spec_from_file_location(
                 fullname,
-                os.path.join(DIR, redir),
+                os.path.join(self.dir, redir),
                 submodule_search_locations=submodule_search_locations,
             )
         if fullname in self.known_source_files:


Test patch:
diff --git a/tests/test_editable_redirect.py b/tests/test_editable_redirect.py
new file mode 100644
index 0000000..b646730
--- /dev/null
+++ b/tests/test_editable_redirect.py
@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+from scikit_build_core.resources._editable_redirect import ScikitBuildRedirectingFinder
+
+
+def process_dict(d: dict[str, str]) -> dict[str, str]:
+    return {k: str(Path(v)) for k, v in d.items()}
+
+
+def process_dict_set(d: dict[str, set[str]]) -> dict[str, set[str]]:
+    return {k: {str(Path(x)) for x in v} for k, v in d.items()}
+
+
+def test_editable_redirect():
+    known_source_files = process_dict(
+        {
+            "pkg": "/source/pkg/__init__.py",
+            "pkg.module": "/source/pkg/module.py",
+            "pkg.subpkg": "/source/pkg/subpkg/__init__.py",
+            "pkg.subpkg.module": "/source/pkg/subpkg/module.py",
+            "pkg.resources.file": "/source/pkg/resources/file.txt",
+            "pkg.namespace.module": "/source/pkg/namespace/module.py",
+        }
+    )
+    known_wheel_files = process_dict(
+        {
+            "pkg.subpkg.source": "pkg/subpkg/source.py",
+            "pkg.src_files": "pkg/src_files.py",
+            "pkg.namespace.source": "pkg/namespace/source.py",
+            "pkg.iresources.file": "pkg/iresources/file.txt",
+            "pkg.installed_files": "pkg/installed_files.py",
+            "pkg.source": "pkg/source.py",
+        }
+    )
+
+    finder = ScikitBuildRedirectingFinder(
+        known_source_files=known_source_files,
+        known_wheel_files=known_wheel_files,
+        path=None,
+        rebuild=False,
+        verbose=False,
+        build_options=[],
+        install_options=[],
+        dir=str(Path("/sitepackages")),
+    )
+
+    assert finder.submodule_search_locations == process_dict_set(
+        {
+            "pkg": {
+                "/sitepackages/pkg",
+                "/source/pkg",
+            },
+            "pkg.iresources": {
+                "/sitepackages/pkg/iresources",
+            },
+            "pkg.namespace": {
+                "/sitepackages/pkg/namespace",
+                "/source/pkg/namespace",
+            },
+            "pkg.resources": {"/source/pkg/resources"},
+            "pkg.subpkg": {
+                "/sitepackages/pkg/subpkg",
+                "/source/pkg/subpkg",
+            },
+        }
+    )
+    assert finder.pkgs == ["pkg", "pkg.subpkg"]
diff --git a/tests/test_editable_unit.py b/tests/test_editable_unit.py
index 5dadd0c..3dcc452 100644
--- a/tests/test_editable_unit.py
+++ b/tests/test_editable_unit.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import sys
 import textwrap
 import typing
 from pathlib import Path
@@ -54,8 +55,8 @@ def editable_package(
     src_pkg_dir.mkdir()
 
     # Make some fake files
-    (src_pkg_dir / "__init__.py").touch()
-    (src_pkg_dir / "module.py").write_text(
+    src_pkg_dir.joinpath("__init__.py").touch()
+    src_pkg_dir.joinpath("module.py").write_text(
         textwrap.dedent(
             f"""\
             from {prefix}.subpkg import module
@@ -65,7 +66,7 @@ def editable_package(
             """
         )
     )
-    (pkg_dir / "source.py").write_text(
+    pkg_dir.joinpath("source.py").write_text(
         textwrap.dedent(
             f"""\
             from {prefix}.subpkg import module
@@ -76,6 +77,36 @@ def editable_package(
         )
     )
 
+    pkg_dir.joinpath("src_files.py").write_text(
+        textwrap.dedent(
+            """\
+            import sys
+
+            from importlib.resources import files
+
+            read_file = files("pkg.resources").joinpath("file.txt").read_text(encoding="utf-8")
+            assert read_file == "hello"
+            """
+        )
+    )
+    resources_dir = src_pkg_dir / "resources"
+    resources_dir.mkdir()
+    resources_dir.joinpath("file.txt").write_text("hello")
+
+    pkg_dir.joinpath("installed_files.py").write_text(
+        textwrap.dedent(
+            """\
+            from importlib.resources import files
+
+            read_file = files("pkg.iresources").joinpath("file.txt").read_text(encoding="utf-8")
+            assert read_file == "hi"
+            """
+        )
+    )
+    iresources_dir = pkg_dir / "iresources"
+    iresources_dir.mkdir()
+    iresources_dir.joinpath("file.txt").write_text("hi")
+
     src_sub_package = src_pkg_dir / "subpkg"
     src_sub_package.mkdir()
     src_sub_package.joinpath("__init__.py").touch()
@@ -96,6 +127,9 @@ def editable_package(
     return EditablePackage(site_packages, pkg_dir, src_pkg_dir)
 
 
+@pytest.mark.xfail(
+    sys.version_info[:2] == (3, 9), reason="Python 3.9 not supported yet"
+)
 def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VEnv):
     site_packages, pkg_dir, src_pkg_dir = editable_package
 
@@ -113,6 +147,7 @@ def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VE
         str(Path("pkg/namespace/module.py")): str(pkg_dir / "namespace/module.py"),
         str(Path("pkg/subpkg/__init__.py")): str(pkg_dir / "subpkg/__init__.py"),
         str(Path("pkg/subpkg/module.py")): str(pkg_dir / "subpkg/module.py"),
+        str(Path("pkg/resources/file.txt")): str(pkg_dir / "resources/file.txt"),
     }
     modules = mapping_to_modules(mapping, libdir=site_packages)
 
@@ -122,6 +157,7 @@ def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VE
         "pkg.namespace.module": str(src_pkg_dir / "namespace/module.py"),
         "pkg.subpkg": str(src_pkg_dir / "subpkg/__init__.py"),
         "pkg.subpkg.module": str(src_pkg_dir / "subpkg/module.py"),
+        "pkg.resources.file": str(src_pkg_dir / "resources/file.txt"),
     }
 
     installed = libdir_to_installed(site_packages)
@@ -131,6 +167,9 @@ def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VE
         "pkg.subpkg.source": str(Path("pkg/subpkg/source.py")),
         "pkg.namespace.source": str(Path("pkg/namespace/source.py")),
         "pkg.source": str(Path("pkg/source.py")),
+        "pkg.installed_files": str(Path("pkg/installed_files.py")),
+        "pkg.iresources.file": str(Path("pkg/iresources/file.txt")),
+        "pkg.src_files": str(Path("pkg/src_files.py")),
     }
 
     editable_txt = editable_redirect(
@@ -152,6 +191,12 @@ def test_navigate_editable_pkg(editable_package: EditablePackage, virtualenv: VE
     virtualenv.execute("import pkg.subpkg.source")
     virtualenv.execute("import pkg.namespace.module")
     virtualenv.execute("import pkg.namespace.source")
+
     # This allows debug print statements in _editable_redirect.py to be seen
     print(virtualenv.execute("import pkg.module"))
     print(virtualenv.execute("import pkg.source"))
+
+    # Load resource files
+    if sys.version_info >= (3, 9):
+        virtualenv.execute("import pkg.src_files")
+        virtualenv.execute("import pkg.installed_files")