]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-106531: Apply changes from importlib_resources 6.3.2 (#117054)
authorJason R. Coombs <jaraco@jaraco.com>
Tue, 4 Jun 2024 06:36:28 +0000 (02:36 -0400)
committerGitHub <noreply@github.com>
Tue, 4 Jun 2024 06:36:28 +0000 (06:36 +0000)
Apply changes from importlib_resources 6.3.2.

16 files changed:
Lib/importlib/resources/_common.py
Lib/importlib/resources/readers.py
Lib/test/test_importlib/resources/data01/subdirectory/binary.file
Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file [new file with mode: 0644]
Lib/test/test_importlib/resources/test_contents.py
Lib/test/test_importlib/resources/test_custom.py
Lib/test/test_importlib/resources/test_files.py
Lib/test/test_importlib/resources/test_open.py
Lib/test/test_importlib/resources/test_path.py
Lib/test/test_importlib/resources/test_read.py
Lib/test/test_importlib/resources/test_reader.py
Lib/test/test_importlib/resources/test_resource.py
Lib/test/test_importlib/resources/util.py
Lib/test/test_importlib/resources/zip.py [new file with mode: 0755]
Makefile.pre.in
Misc/NEWS.d/next/Library/2024-03-19-21-41-31.gh-issue-106531.Mgd--6.rst [new file with mode: 0644]

index e18082fb3d26a056f43804110a51df5a029f62f7..ca5b06743b46a697a6a0d96da00816696bf129e5 100644 (file)
@@ -25,6 +25,8 @@ def package_to_anchor(func):
     >>> files('a', 'b')
     Traceback (most recent call last):
     TypeError: files() takes from 0 to 1 positional arguments but 2 were given
+
+    Remove this compatibility in Python 3.14.
     """
     undefined = object()
 
index c3cdf769cbecb00c20da5ae8fad3a60f737a27d1..b86cdeff57c4c2ea14d5e7cc8069126355ad3166 100644 (file)
@@ -1,7 +1,10 @@
 import collections
+import contextlib
 import itertools
 import pathlib
 import operator
+import re
+import warnings
 import zipfile
 
 from . import abc
@@ -62,7 +65,7 @@ class MultiplexedPath(abc.Traversable):
     """
 
     def __init__(self, *paths):
-        self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
+        self._paths = list(map(_ensure_traversable, remove_duplicates(paths)))
         if not self._paths:
             message = 'MultiplexedPath must contain at least one path'
             raise FileNotFoundError(message)
@@ -130,7 +133,36 @@ class NamespaceReader(abc.TraversableResources):
     def __init__(self, namespace_path):
         if 'NamespacePath' not in str(namespace_path):
             raise ValueError('Invalid path')
-        self.path = MultiplexedPath(*list(namespace_path))
+        self.path = MultiplexedPath(*map(self._resolve, namespace_path))
+
+    @classmethod
+    def _resolve(cls, path_str) -> abc.Traversable:
+        r"""
+        Given an item from a namespace path, resolve it to a Traversable.
+
+        path_str might be a directory on the filesystem or a path to a
+        zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
+        ``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
+        """
+        (dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
+        return dir
+
+    @classmethod
+    def _candidate_paths(cls, path_str):
+        yield pathlib.Path(path_str)
+        yield from cls._resolve_zip_path(path_str)
+
+    @staticmethod
+    def _resolve_zip_path(path_str):
+        for match in reversed(list(re.finditer(r'[\\/]', path_str))):
+            with contextlib.suppress(
+                FileNotFoundError,
+                IsADirectoryError,
+                NotADirectoryError,
+                PermissionError,
+            ):
+                inner = path_str[match.end() :].replace('\\', '/') + '/'
+                yield zipfile.Path(path_str[: match.start()], inner.lstrip('/'))
 
     def resource_path(self, resource):
         """
@@ -142,3 +174,21 @@ class NamespaceReader(abc.TraversableResources):
 
     def files(self):
         return self.path
+
+
+def _ensure_traversable(path):
+    """
+    Convert deprecated string arguments to traversables (pathlib.Path).
+
+    Remove with Python 3.15.
+    """
+    if not isinstance(path, str):
+        return path
+
+    warnings.warn(
+        "String arguments are deprecated. Pass a Traversable instead.",
+        DeprecationWarning,
+        stacklevel=3,
+    )
+
+    return pathlib.Path(path)
index eaf36c1daccfdf325514461cd1a2ffbc139b5464..5bd8bb897b13225c93a1d26baa88c96b7bd5d817 100644 (file)
Binary files a/Lib/test/test_importlib/resources/data01/subdirectory/binary.file and b/Lib/test/test_importlib/resources/data01/subdirectory/binary.file differ
diff --git a/Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file b/Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file
new file mode 100644 (file)
index 0000000..100f506
--- /dev/null
@@ -0,0 +1 @@
+\f\r\ e\ f
\ No newline at end of file
index 1a13f043a86f034d709810289d9d524f734fbf33..beab67ccc2168026e3c489054a9918f9cf22c155 100644 (file)
@@ -31,8 +31,8 @@ class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
 class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
     expected = {
         # no __init__ because of namespace design
-        # no subdirectory as incidental difference in fixture
         'binary.file',
+        'subdirectory',
         'utf-16.file',
         'utf-8.file',
     }
index 73127209a2761b6c52291e876a532d346c6532a4..640f90fc0dd91a353cbef31ad7339288d9a52e41 100644 (file)
@@ -5,6 +5,7 @@ import pathlib
 from test.support import os_helper
 
 from importlib import resources
+from importlib.resources import abc
 from importlib.resources.abc import TraversableResources, ResourceReader
 from . import util
 
@@ -39,8 +40,9 @@ class CustomTraversableResourcesTests(unittest.TestCase):
         self.addCleanup(self.fixtures.close)
 
     def test_custom_loader(self):
-        temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
+        temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir()))
         loader = SimpleLoader(MagicResources(temp_dir))
         pkg = util.create_package_from_loader(loader)
         files = resources.files(pkg)
-        assert files is temp_dir
+        assert isinstance(files, abc.Traversable)
+        assert list(files.iterdir()) == []
index 26c8b04e44c3b9b3ef34116419ff4c3ea59d6c00..7df6d03ead74808a9de89682632d2188240fa7e3 100644 (file)
@@ -1,4 +1,3 @@
-import typing
 import textwrap
 import unittest
 import warnings
@@ -32,13 +31,14 @@ class FilesTests:
         actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
         assert actual == 'Hello, UTF-8 world!\n'
 
-    @unittest.skipUnless(
-        hasattr(typing, 'runtime_checkable'),
-        "Only suitable when typing supports runtime_checkable",
-    )
     def test_traversable(self):
         assert isinstance(resources.files(self.data), Traversable)
 
+    def test_joinpath_with_multiple_args(self):
+        files = resources.files(self.data)
+        binfile = files.joinpath('subdirectory', 'binary.file')
+        self.assertTrue(binfile.is_file())
+
     def test_old_parameter(self):
         """
         Files used to take a 'package' parameter. Make sure anyone
@@ -64,6 +64,10 @@ class OpenNamespaceTests(FilesTests, unittest.TestCase):
         self.data = namespacedata01
 
 
+class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
+    ZIP_MODULE = 'namespacedata01'
+
+
 class SiteDir:
     def setUp(self):
         self.fixtures = contextlib.ExitStack()
index 86becb4bfaad37990e010f3a5be30cf377307380..3b6b2142ef47b18aa72979bb55788fb95899fafb 100644 (file)
@@ -24,7 +24,7 @@ class OpenTests:
         target = resources.files(self.data) / 'binary.file'
         with target.open('rb') as fp:
             result = fp.read()
-            self.assertEqual(result, b'\x00\x01\x02\x03')
+            self.assertEqual(result, bytes(range(4)))
 
     def test_open_text_default_encoding(self):
         target = resources.files(self.data) / 'utf-8.file'
@@ -81,5 +81,9 @@ class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
     pass
 
 
+class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
+    ZIP_MODULE = 'namespacedata01'
+
+
 if __name__ == '__main__':
     unittest.main()
index 34a6bdd2d58b916358f8b099fcadb2d3769da53c..90b22905ab869235591c77d9239e79bb5d0f1ee9 100644 (file)
@@ -1,4 +1,5 @@
 import io
+import pathlib
 import unittest
 
 from importlib import resources
@@ -15,18 +16,13 @@ class CommonTests(util.CommonTests, unittest.TestCase):
 class PathTests:
     def test_reading(self):
         """
-        Path should be readable.
-
-        Test also implicitly verifies the returned object is a pathlib.Path
-        instance.
+        Path should be readable and a pathlib.Path instance.
         """
         target = resources.files(self.data) / 'utf-8.file'
         with resources.as_file(target) as path:
+            self.assertIsInstance(path, pathlib.Path)
             self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
-            # pathlib.Path.read_text() was introduced in Python 3.5.
-            with path.open('r', encoding='utf-8') as file:
-                text = file.read()
-            self.assertEqual('Hello, UTF-8 world!\n', text)
+            self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
 
 
 class PathDiskTests(PathTests, unittest.TestCase):
index 088982681e8b0cd6f1f9697f4ccc8bc8847d9d6f..984feecbb9ed6948d1cc1b80cf516eb346276a29 100644 (file)
@@ -18,7 +18,7 @@ class CommonTextTests(util.CommonTests, unittest.TestCase):
 class ReadTests:
     def test_read_bytes(self):
         result = resources.files(self.data).joinpath('binary.file').read_bytes()
-        self.assertEqual(result, b'\0\1\2\3')
+        self.assertEqual(result, bytes(range(4)))
 
     def test_read_text_default_encoding(self):
         result = (
@@ -57,17 +57,15 @@ class ReadDiskTests(ReadTests, unittest.TestCase):
 
 class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
     def test_read_submodule_resource(self):
-        submodule = import_module('ziptestdata.subdirectory')
+        submodule = import_module('data01.subdirectory')
         result = resources.files(submodule).joinpath('binary.file').read_bytes()
-        self.assertEqual(result, b'\0\1\2\3')
+        self.assertEqual(result, bytes(range(4, 8)))
 
     def test_read_submodule_resource_by_name(self):
         result = (
-            resources.files('ziptestdata.subdirectory')
-            .joinpath('binary.file')
-            .read_bytes()
+            resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
         )
-        self.assertEqual(result, b'\0\1\2\3')
+        self.assertEqual(result, bytes(range(4, 8)))
 
 
 class ReadNamespaceTests(ReadTests, unittest.TestCase):
@@ -77,5 +75,22 @@ class ReadNamespaceTests(ReadTests, unittest.TestCase):
         self.data = namespacedata01
 
 
+class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
+    ZIP_MODULE = 'namespacedata01'
+
+    def test_read_submodule_resource(self):
+        submodule = import_module('namespacedata01.subdirectory')
+        result = resources.files(submodule).joinpath('binary.file').read_bytes()
+        self.assertEqual(result, bytes(range(12, 16)))
+
+    def test_read_submodule_resource_by_name(self):
+        result = (
+            resources.files('namespacedata01.subdirectory')
+            .joinpath('binary.file')
+            .read_bytes()
+        )
+        self.assertEqual(result, bytes(range(12, 16)))
+
+
 if __name__ == '__main__':
     unittest.main()
index 8670f72a33458552bfb551829bb0820f623ff1fa..dac9c2a892ffd245ba3b8956ef885f04b0888f63 100644 (file)
@@ -10,8 +10,7 @@ from importlib.readers import MultiplexedPath, NamespaceReader
 class MultiplexedPathTest(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        path = pathlib.Path(__file__).parent / 'namespacedata01'
-        cls.folder = str(path)
+        cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
 
     def test_init_no_paths(self):
         with self.assertRaises(FileNotFoundError):
@@ -19,7 +18,7 @@ class MultiplexedPathTest(unittest.TestCase):
 
     def test_init_file(self):
         with self.assertRaises(NotADirectoryError):
-            MultiplexedPath(os.path.join(self.folder, 'binary.file'))
+            MultiplexedPath(self.folder / 'binary.file')
 
     def test_iterdir(self):
         contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
@@ -27,10 +26,12 @@ class MultiplexedPathTest(unittest.TestCase):
             contents.remove('__pycache__')
         except (KeyError, ValueError):
             pass
-        self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'})
+        self.assertEqual(
+            contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
+        )
 
     def test_iterdir_duplicate(self):
-        data01 = os.path.abspath(os.path.join(__file__, '..', 'data01'))
+        data01 = pathlib.Path(__file__).parent.joinpath('data01')
         contents = {
             path.name for path in MultiplexedPath(self.folder, data01).iterdir()
         }
@@ -60,17 +61,17 @@ class MultiplexedPathTest(unittest.TestCase):
             path.open()
 
     def test_join_path(self):
-        prefix = os.path.abspath(os.path.join(__file__, '..'))
-        data01 = os.path.join(prefix, 'data01')
+        data01 = pathlib.Path(__file__).parent.joinpath('data01')
+        prefix = str(data01.parent)
         path = MultiplexedPath(self.folder, data01)
         self.assertEqual(
             str(path.joinpath('binary.file'))[len(prefix) + 1 :],
             os.path.join('namespacedata01', 'binary.file'),
         )
-        self.assertEqual(
-            str(path.joinpath('subdirectory'))[len(prefix) + 1 :],
-            os.path.join('data01', 'subdirectory'),
-        )
+        sub = path.joinpath('subdirectory')
+        assert isinstance(sub, MultiplexedPath)
+        assert 'namespacedata01' in str(sub)
+        assert 'data01' in str(sub)
         self.assertEqual(
             str(path.joinpath('imaginary'))[len(prefix) + 1 :],
             os.path.join('namespacedata01', 'imaginary'),
@@ -82,9 +83,9 @@ class MultiplexedPathTest(unittest.TestCase):
         assert not path.joinpath('imaginary/foo.py').exists()
 
     def test_join_path_common_subdir(self):
-        prefix = os.path.abspath(os.path.join(__file__, '..'))
-        data01 = os.path.join(prefix, 'data01')
-        data02 = os.path.join(prefix, 'data02')
+        data01 = pathlib.Path(__file__).parent.joinpath('data01')
+        data02 = pathlib.Path(__file__).parent.joinpath('data02')
+        prefix = str(data01.parent)
         path = MultiplexedPath(data01, data02)
         self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
         self.assertEqual(
index 6f75cf57f03d02da69b2315c74389092760f94a5..d1d45d9b4617f3534fe92b1c8b23cd4b6365798b 100644 (file)
@@ -1,15 +1,10 @@
-import contextlib
 import sys
 import unittest
-import uuid
 import pathlib
 
 from . import data01
-from . import zipdata01, zipdata02
 from . import util
 from importlib import resources, import_module
-from test.support import import_helper, os_helper
-from test.support.os_helper import unlink
 
 
 class ResourceTests:
@@ -89,34 +84,32 @@ class ResourceCornerCaseTests(unittest.TestCase):
 
 
 class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
-    ZIP_MODULE = zipdata01  # type: ignore
+    ZIP_MODULE = 'data01'
 
     def test_is_submodule_resource(self):
-        submodule = import_module('ziptestdata.subdirectory')
+        submodule = import_module('data01.subdirectory')
         self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
 
     def test_read_submodule_resource_by_name(self):
         self.assertTrue(
-            resources.files('ziptestdata.subdirectory')
-            .joinpath('binary.file')
-            .is_file()
+            resources.files('data01.subdirectory').joinpath('binary.file').is_file()
         )
 
     def test_submodule_contents(self):
-        submodule = import_module('ziptestdata.subdirectory')
+        submodule = import_module('data01.subdirectory')
         self.assertEqual(
             names(resources.files(submodule)), {'__init__.py', 'binary.file'}
         )
 
     def test_submodule_contents_by_name(self):
         self.assertEqual(
-            names(resources.files('ziptestdata.subdirectory')),
+            names(resources.files('data01.subdirectory')),
             {'__init__.py', 'binary.file'},
         )
 
     def test_as_file_directory(self):
-        with resources.as_file(resources.files('ziptestdata')) as data:
-            assert data.name == 'ziptestdata'
+        with resources.as_file(resources.files('data01')) as data:
+            assert data.name == 'data01'
             assert data.is_dir()
             assert data.joinpath('subdirectory').is_dir()
             assert len(list(data.iterdir()))
@@ -124,7 +117,7 @@ class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
 
 
 class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
-    ZIP_MODULE = zipdata02  # type: ignore
+    ZIP_MODULE = 'data02'
 
     def test_unrelated_contents(self):
         """
@@ -132,93 +125,48 @@ class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
         distinct resources. Ref python/importlib_resources#44.
         """
         self.assertEqual(
-            names(resources.files('ziptestdata.one')),
+            names(resources.files('data02.one')),
             {'__init__.py', 'resource1.txt'},
         )
         self.assertEqual(
-            names(resources.files('ziptestdata.two')),
+            names(resources.files('data02.two')),
             {'__init__.py', 'resource2.txt'},
         )
 
 
-@contextlib.contextmanager
-def zip_on_path(dir):
-    data_path = pathlib.Path(zipdata01.__file__)
-    source_zip_path = data_path.parent.joinpath('ziptestdata.zip')
-    zip_path = pathlib.Path(dir) / f'{uuid.uuid4()}.zip'
-    zip_path.write_bytes(source_zip_path.read_bytes())
-    sys.path.append(str(zip_path))
-    import_module('ziptestdata')
-
-    try:
-        yield
-    finally:
-        with contextlib.suppress(ValueError):
-            sys.path.remove(str(zip_path))
-
-        with contextlib.suppress(KeyError):
-            del sys.path_importer_cache[str(zip_path)]
-            del sys.modules['ziptestdata']
-
-        with contextlib.suppress(OSError):
-            unlink(zip_path)
-
-
-class DeletingZipsTest(unittest.TestCase):
+class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase):
     """Having accessed resources in a zip file should not keep an open
     reference to the zip.
     """
 
-    def setUp(self):
-        self.fixtures = contextlib.ExitStack()
-        self.addCleanup(self.fixtures.close)
-
-        modules = import_helper.modules_setup()
-        self.addCleanup(import_helper.modules_cleanup, *modules)
-
-        temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
-        self.fixtures.enter_context(zip_on_path(temp_dir))
-
     def test_iterdir_does_not_keep_open(self):
-        [item.name for item in resources.files('ziptestdata').iterdir()]
+        [item.name for item in resources.files('data01').iterdir()]
 
     def test_is_file_does_not_keep_open(self):
-        resources.files('ziptestdata').joinpath('binary.file').is_file()
+        resources.files('data01').joinpath('binary.file').is_file()
 
     def test_is_file_failure_does_not_keep_open(self):
-        resources.files('ziptestdata').joinpath('not-present').is_file()
+        resources.files('data01').joinpath('not-present').is_file()
 
     @unittest.skip("Desired but not supported.")
     def test_as_file_does_not_keep_open(self):  # pragma: no cover
-        resources.as_file(resources.files('ziptestdata') / 'binary.file')
+        resources.as_file(resources.files('data01') / 'binary.file')
 
     def test_entered_path_does_not_keep_open(self):
         """
         Mimic what certifi does on import to make its bundle
         available for the process duration.
         """
-        resources.as_file(resources.files('ziptestdata') / 'binary.file').__enter__()
+        resources.as_file(resources.files('data01') / 'binary.file').__enter__()
 
     def test_read_binary_does_not_keep_open(self):
-        resources.files('ziptestdata').joinpath('binary.file').read_bytes()
+        resources.files('data01').joinpath('binary.file').read_bytes()
 
     def test_read_text_does_not_keep_open(self):
-        resources.files('ziptestdata').joinpath('utf-8.file').read_text(
-            encoding='utf-8'
-        )
+        resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8')
 
 
-class ResourceFromNamespaceTest01(unittest.TestCase):
-    site_dir = str(pathlib.Path(__file__).parent)
-
-    @classmethod
-    def setUpClass(cls):
-        sys.path.append(cls.site_dir)
-
-    @classmethod
-    def tearDownClass(cls):
-        sys.path.remove(cls.site_dir)
-
+class ResourceFromNamespaceTests:
     def test_is_submodule_resource(self):
         self.assertTrue(
             resources.files(import_module('namespacedata01'))
@@ -237,7 +185,9 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
             contents.remove('__pycache__')
         except KeyError:
             pass
-        self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'})
+        self.assertEqual(
+            contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
+        )
 
     def test_submodule_contents_by_name(self):
         contents = names(resources.files('namespacedata01'))
@@ -245,7 +195,45 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
             contents.remove('__pycache__')
         except KeyError:
             pass
-        self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'})
+        self.assertEqual(
+            contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
+        )
+
+    def test_submodule_sub_contents(self):
+        contents = names(resources.files(import_module('namespacedata01.subdirectory')))
+        try:
+            contents.remove('__pycache__')
+        except KeyError:
+            pass
+        self.assertEqual(contents, {'binary.file'})
+
+    def test_submodule_sub_contents_by_name(self):
+        contents = names(resources.files('namespacedata01.subdirectory'))
+        try:
+            contents.remove('__pycache__')
+        except KeyError:
+            pass
+        self.assertEqual(contents, {'binary.file'})
+
+
+class ResourceFromNamespaceDiskTests(ResourceFromNamespaceTests, unittest.TestCase):
+    site_dir = str(pathlib.Path(__file__).parent)
+
+    @classmethod
+    def setUpClass(cls):
+        sys.path.append(cls.site_dir)
+
+    @classmethod
+    def tearDownClass(cls):
+        sys.path.remove(cls.site_dir)
+
+
+class ResourceFromNamespaceZipTests(
+    util.ZipSetupBase,
+    ResourceFromNamespaceTests,
+    unittest.TestCase,
+):
+    ZIP_MODULE = 'namespacedata01'
 
 
 if __name__ == '__main__':
index dbe6ee8147669931b71ed91616606241efdec072..d4bf3e6cc5dfdcc9a62fa8774035a9fb54ba76be 100644 (file)
@@ -4,11 +4,12 @@ import io
 import sys
 import types
 import pathlib
+import contextlib
 
 from . import data01
-from . import zipdata01
 from importlib.resources.abc import ResourceReader
-from test.support import import_helper
+from test.support import import_helper, os_helper
+from . import zip as zip_
 
 
 from importlib.machinery import ModuleSpec
@@ -141,39 +142,23 @@ class CommonTests(metaclass=abc.ABCMeta):
 
 
 class ZipSetupBase:
-    ZIP_MODULE = None
-
-    @classmethod
-    def setUpClass(cls):
-        data_path = pathlib.Path(cls.ZIP_MODULE.__file__)
-        data_dir = data_path.parent
-        cls._zip_path = str(data_dir / 'ziptestdata.zip')
-        sys.path.append(cls._zip_path)
-        cls.data = importlib.import_module('ziptestdata')
-
-    @classmethod
-    def tearDownClass(cls):
-        try:
-            sys.path.remove(cls._zip_path)
-        except ValueError:
-            pass
-
-        try:
-            del sys.path_importer_cache[cls._zip_path]
-            del sys.modules[cls.data.__name__]
-        except KeyError:
-            pass
-
-        try:
-            del cls.data
-            del cls._zip_path
-        except AttributeError:
-            pass
+    ZIP_MODULE = 'data01'
 
     def setUp(self):
-        modules = import_helper.modules_setup()
-        self.addCleanup(import_helper.modules_cleanup, *modules)
+        self.fixtures = contextlib.ExitStack()
+        self.addCleanup(self.fixtures.close)
+
+        self.fixtures.enter_context(import_helper.isolated_modules())
+
+        temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
+        modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
+        src_path = pathlib.Path(__file__).parent.joinpath(self.ZIP_MODULE)
+        self.fixtures.enter_context(
+            import_helper.DirsOnSysPath(str(zip_.make_zip_file(src_path, modules)))
+        )
+
+        self.data = importlib.import_module(self.ZIP_MODULE)
 
 
 class ZipSetup(ZipSetupBase):
-    ZIP_MODULE = zipdata01  # type: ignore
+    pass
diff --git a/Lib/test/test_importlib/resources/zip.py b/Lib/test/test_importlib/resources/zip.py
new file mode 100755 (executable)
index 0000000..4dcf6fa
--- /dev/null
@@ -0,0 +1,30 @@
+"""
+Generate zip test data files.
+"""
+
+import contextlib
+import os
+import pathlib
+import zipfile
+
+
+def make_zip_file(src, dst):
+    """
+    Zip the files in src into a new zipfile at dst.
+    """
+    with zipfile.ZipFile(dst, 'w') as zf:
+        for src_path, rel in walk(src):
+            dst_name = src.name / pathlib.PurePosixPath(rel.as_posix())
+            zf.write(src_path, dst_name)
+        zipfile._path.CompleteDirs.inject(zf)
+    return dst
+
+
+def walk(datapath):
+    for dirpath, dirnames, filenames in os.walk(datapath):
+        with contextlib.suppress(ValueError):
+            dirnames.remove('__pycache__')
+        for filename in filenames:
+            res = pathlib.Path(dirpath) / filename
+            rel = res.relative_to(datapath)
+            yield res, rel
index 9a2fc34f03066280e66b0992ca1165c9ca5be460..22dba279faa93581d43f6c3e0fa66e29bfc4f60c 100644 (file)
@@ -2438,6 +2438,7 @@ TESTSUBDIRS=      idlelib/idle_test \
                test/test_importlib/resources/data03/namespace/portion1 \
                test/test_importlib/resources/data03/namespace/portion2 \
                test/test_importlib/resources/namespacedata01 \
+               test/test_importlib/resources/namespacedata01/subdirectory \
                test/test_importlib/resources/zipdata01 \
                test/test_importlib/resources/zipdata02 \
                test/test_importlib/source \
diff --git a/Misc/NEWS.d/next/Library/2024-03-19-21-41-31.gh-issue-106531.Mgd--6.rst b/Misc/NEWS.d/next/Library/2024-03-19-21-41-31.gh-issue-106531.Mgd--6.rst
new file mode 100644 (file)
index 0000000..6a5783c
--- /dev/null
@@ -0,0 +1,6 @@
+In :mod:`importlib.resources`, sync with `importlib_resources 6.3.2
+<https://importlib-resources.readthedocs.io/en/latest/history.html#v6-3-2>`_,
+including: ``MultiplexedPath`` now expects ``Traversable`` paths,
+deprecating string arguments to ``MultiplexedPath``; Enabled support for
+resources in namespace packages in zip files; Fixed ``NotADirectoryError``
+when calling files on a subdirectory of a namespace package.