aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-06-04 16:54:59 +0200
committerGitHub <noreply@github.com>2024-06-04 10:54:59 -0400
commit23ebf87eaa88d43648bb99f84809485c45b39b0b (patch)
tree51c145e3b784536062628cc8884d9840ee370f93
parent[3.13] gh-117657: Fix race involving GC and heap initialization (GH-119923) (... (diff)
downloadcpython-23ebf87eaa88d43648bb99f84809485c45b39b0b.tar.gz
cpython-23ebf87eaa88d43648bb99f84809485c45b39b0b.tar.bz2
cpython-23ebf87eaa88d43648bb99f84809485c45b39b0b.zip
[3.13] gh-106531: Apply changes from importlib_resources 6.3.2 (GH-117054) (#120014)
gh-106531: Apply changes from importlib_resources 6.3.2 (GH-117054) Apply changes from importlib_resources 6.3.2. (cherry picked from commit 8d63c8d47b9edd8ac2f0b395b2fa0ae5f571252d) Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
-rw-r--r--Lib/importlib/resources/_common.py2
-rw-r--r--Lib/importlib/resources/readers.py54
-rw-r--r--Lib/test/test_importlib/resources/data01/subdirectory/binary.filebin4 -> 4 bytes
-rw-r--r--Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file1
-rw-r--r--Lib/test/test_importlib/resources/test_contents.py2
-rw-r--r--Lib/test/test_importlib/resources/test_custom.py6
-rw-r--r--Lib/test/test_importlib/resources/test_files.py14
-rw-r--r--Lib/test/test_importlib/resources/test_open.py6
-rw-r--r--Lib/test/test_importlib/resources/test_path.py12
-rw-r--r--Lib/test/test_importlib/resources/test_read.py29
-rw-r--r--Lib/test/test_importlib/resources/test_reader.py29
-rw-r--r--Lib/test/test_importlib/resources/test_resource.py134
-rw-r--r--Lib/test/test_importlib/resources/util.py51
-rwxr-xr-xLib/test/test_importlib/resources/zip.py30
-rw-r--r--Makefile.pre.in1
-rw-r--r--Misc/NEWS.d/next/Library/2024-03-19-21-41-31.gh-issue-106531.Mgd--6.rst6
16 files changed, 231 insertions, 146 deletions
diff --git a/Lib/importlib/resources/_common.py b/Lib/importlib/resources/_common.py
index e18082fb3d2..ca5b06743b4 100644
--- a/Lib/importlib/resources/_common.py
+++ b/Lib/importlib/resources/_common.py
@@ -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()
diff --git a/Lib/importlib/resources/readers.py b/Lib/importlib/resources/readers.py
index c3cdf769cbe..b86cdeff57c 100644
--- a/Lib/importlib/resources/readers.py
+++ b/Lib/importlib/resources/readers.py
@@ -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)
diff --git a/Lib/test/test_importlib/resources/data01/subdirectory/binary.file b/Lib/test/test_importlib/resources/data01/subdirectory/binary.file
index eaf36c1dacc..5bd8bb897b1 100644
--- a/Lib/test/test_importlib/resources/data01/subdirectory/binary.file
+++ b/Lib/test/test_importlib/resources/data01/subdirectory/binary.file
Binary files 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
index 00000000000..100f50643d8
--- /dev/null
+++ b/Lib/test/test_importlib/resources/namespacedata01/subdirectory/binary.file
@@ -0,0 +1 @@
+  \ No newline at end of file
diff --git a/Lib/test/test_importlib/resources/test_contents.py b/Lib/test/test_importlib/resources/test_contents.py
index 1a13f043a86..beab67ccc21 100644
--- a/Lib/test/test_importlib/resources/test_contents.py
+++ b/Lib/test/test_importlib/resources/test_contents.py
@@ -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',
}
diff --git a/Lib/test/test_importlib/resources/test_custom.py b/Lib/test/test_importlib/resources/test_custom.py
index 73127209a27..640f90fc0dd 100644
--- a/Lib/test/test_importlib/resources/test_custom.py
+++ b/Lib/test/test_importlib/resources/test_custom.py
@@ -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()) == []
diff --git a/Lib/test/test_importlib/resources/test_files.py b/Lib/test/test_importlib/resources/test_files.py
index 26c8b04e44c..7df6d03ead7 100644
--- a/Lib/test/test_importlib/resources/test_files.py
+++ b/Lib/test/test_importlib/resources/test_files.py
@@ -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()
diff --git a/Lib/test/test_importlib/resources/test_open.py b/Lib/test/test_importlib/resources/test_open.py
index 86becb4bfaa..3b6b2142ef4 100644
--- a/Lib/test/test_importlib/resources/test_open.py
+++ b/Lib/test/test_importlib/resources/test_open.py
@@ -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()
diff --git a/Lib/test/test_importlib/resources/test_path.py b/Lib/test/test_importlib/resources/test_path.py
index 34a6bdd2d58..90b22905ab8 100644
--- a/Lib/test/test_importlib/resources/test_path.py
+++ b/Lib/test/test_importlib/resources/test_path.py
@@ -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):
diff --git a/Lib/test/test_importlib/resources/test_read.py b/Lib/test/test_importlib/resources/test_read.py
index 088982681e8..984feecbb9e 100644
--- a/Lib/test/test_importlib/resources/test_read.py
+++ b/Lib/test/test_importlib/resources/test_read.py
@@ -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()
diff --git a/Lib/test/test_importlib/resources/test_reader.py b/Lib/test/test_importlib/resources/test_reader.py
index 8670f72a334..dac9c2a892f 100644
--- a/Lib/test/test_importlib/resources/test_reader.py
+++ b/Lib/test/test_importlib/resources/test_reader.py
@@ -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(
diff --git a/Lib/test/test_importlib/resources/test_resource.py b/Lib/test/test_importlib/resources/test_resource.py
index 6f75cf57f03..d1d45d9b461 100644
--- a/Lib/test/test_importlib/resources/test_resource.py
+++ b/Lib/test/test_importlib/resources/test_resource.py
@@ -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__':
diff --git a/Lib/test/test_importlib/resources/util.py b/Lib/test/test_importlib/resources/util.py
index dbe6ee81476..d4bf3e6cc5d 100644
--- a/Lib/test/test_importlib/resources/util.py
+++ b/Lib/test/test_importlib/resources/util.py
@@ -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
index 00000000000..4dcf6facc77
--- /dev/null
+++ b/Lib/test/test_importlib/resources/zip.py
@@ -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
diff --git a/Makefile.pre.in b/Makefile.pre.in
index a3e8f121593..a8a57d26558 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -2436,6 +2436,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
index 00000000000..6a5783c5ad9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-03-19-21-41-31.gh-issue-106531.Mgd--6.rst
@@ -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.