From 54b25713d122a176cc8e7626ae4113d635b9fc14 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 16 Nov 2023 00:56:19 -0500 Subject: [PATCH] fix: correct editable template & add more tests (#552) 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. Signed-off-by: Henry Schreiner --- src/scikit_build_core/build/_editable.py | 2 +- .../resources/_editable_redirect.py | 6 +- tests/test_editable_redirect.py | 69 +++++++++++++++++++ tests/test_editable_unit.py | 51 +++++++++++++- 4 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/test_editable_redirect.py diff --git a/src/scikit_build_core/build/_editable.py b/src/scikit_build_core/build/_editable.py index 93f9b25b..5cb1ae46 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 2f0c2b94..af75519f 100644 --- a/src/scikit_build_core/resources/_editable_redirect.py +++ b/src/scikit_build_core/resources/_editable_redirect.py @@ -32,6 +32,7 @@ def __init__( 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 @@ def __init__( 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 @@ def __init__( 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 @@ def find_spec( 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: diff --git a/tests/test_editable_redirect.py b/tests/test_editable_redirect.py new file mode 100644 index 00000000..b646730d --- /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 5dadd0c0..3dcc4524 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")