[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 8d63c8d47b)

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
Miss Islington (bot) 2024-06-04 16:54:59 +02:00 committed by GitHub
parent ee7d2681f6
commit 23ebf87eaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 231 additions and 146 deletions

View file

@ -25,6 +25,8 @@ def package_to_anchor(func):
>>> files('a', 'b') >>> files('a', 'b')
Traceback (most recent call last): Traceback (most recent call last):
TypeError: files() takes from 0 to 1 positional arguments but 2 were given TypeError: files() takes from 0 to 1 positional arguments but 2 were given
Remove this compatibility in Python 3.14.
""" """
undefined = object() undefined = object()

View file

@ -1,7 +1,10 @@
import collections import collections
import contextlib
import itertools import itertools
import pathlib import pathlib
import operator import operator
import re
import warnings
import zipfile import zipfile
from . import abc from . import abc
@ -62,7 +65,7 @@ class MultiplexedPath(abc.Traversable):
""" """
def __init__(self, *paths): 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: if not self._paths:
message = 'MultiplexedPath must contain at least one path' message = 'MultiplexedPath must contain at least one path'
raise FileNotFoundError(message) raise FileNotFoundError(message)
@ -130,7 +133,36 @@ class NamespaceReader(abc.TraversableResources):
def __init__(self, namespace_path): def __init__(self, namespace_path):
if 'NamespacePath' not in str(namespace_path): if 'NamespacePath' not in str(namespace_path):
raise ValueError('Invalid 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): def resource_path(self, resource):
""" """
@ -142,3 +174,21 @@ class NamespaceReader(abc.TraversableResources):
def files(self): def files(self):
return self.path 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)

View file

@ -31,8 +31,8 @@ class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
class ContentsNamespaceTests(ContentsTests, unittest.TestCase): class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
expected = { expected = {
# no __init__ because of namespace design # no __init__ because of namespace design
# no subdirectory as incidental difference in fixture
'binary.file', 'binary.file',
'subdirectory',
'utf-16.file', 'utf-16.file',
'utf-8.file', 'utf-8.file',
} }

View file

@ -5,6 +5,7 @@ import pathlib
from test.support import os_helper from test.support import os_helper
from importlib import resources from importlib import resources
from importlib.resources import abc
from importlib.resources.abc import TraversableResources, ResourceReader from importlib.resources.abc import TraversableResources, ResourceReader
from . import util from . import util
@ -39,8 +40,9 @@ class CustomTraversableResourcesTests(unittest.TestCase):
self.addCleanup(self.fixtures.close) self.addCleanup(self.fixtures.close)
def test_custom_loader(self): 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)) loader = SimpleLoader(MagicResources(temp_dir))
pkg = util.create_package_from_loader(loader) pkg = util.create_package_from_loader(loader)
files = resources.files(pkg) files = resources.files(pkg)
assert files is temp_dir assert isinstance(files, abc.Traversable)
assert list(files.iterdir()) == []

View file

@ -1,4 +1,3 @@
import typing
import textwrap import textwrap
import unittest import unittest
import warnings import warnings
@ -32,13 +31,14 @@ class FilesTests:
actual = files.joinpath('utf-8.file').read_text(encoding='utf-8') actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
assert actual == 'Hello, UTF-8 world!\n' assert actual == 'Hello, UTF-8 world!\n'
@unittest.skipUnless(
hasattr(typing, 'runtime_checkable'),
"Only suitable when typing supports runtime_checkable",
)
def test_traversable(self): def test_traversable(self):
assert isinstance(resources.files(self.data), Traversable) 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): def test_old_parameter(self):
""" """
Files used to take a 'package' parameter. Make sure anyone Files used to take a 'package' parameter. Make sure anyone
@ -64,6 +64,10 @@ class OpenNamespaceTests(FilesTests, unittest.TestCase):
self.data = namespacedata01 self.data = namespacedata01
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
ZIP_MODULE = 'namespacedata01'
class SiteDir: class SiteDir:
def setUp(self): def setUp(self):
self.fixtures = contextlib.ExitStack() self.fixtures = contextlib.ExitStack()

View file

@ -24,7 +24,7 @@ class OpenTests:
target = resources.files(self.data) / 'binary.file' target = resources.files(self.data) / 'binary.file'
with target.open('rb') as fp: with target.open('rb') as fp:
result = fp.read() result = fp.read()
self.assertEqual(result, b'\x00\x01\x02\x03') self.assertEqual(result, bytes(range(4)))
def test_open_text_default_encoding(self): def test_open_text_default_encoding(self):
target = resources.files(self.data) / 'utf-8.file' target = resources.files(self.data) / 'utf-8.file'
@ -81,5 +81,9 @@ class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
pass pass
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
ZIP_MODULE = 'namespacedata01'
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -1,4 +1,5 @@
import io import io
import pathlib
import unittest import unittest
from importlib import resources from importlib import resources
@ -15,18 +16,13 @@ class CommonTests(util.CommonTests, unittest.TestCase):
class PathTests: class PathTests:
def test_reading(self): def test_reading(self):
""" """
Path should be readable. Path should be readable and a pathlib.Path instance.
Test also implicitly verifies the returned object is a pathlib.Path
instance.
""" """
target = resources.files(self.data) / 'utf-8.file' target = resources.files(self.data) / 'utf-8.file'
with resources.as_file(target) as path: with resources.as_file(target) as path:
self.assertIsInstance(path, pathlib.Path)
self.assertTrue(path.name.endswith("utf-8.file"), repr(path)) self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
# pathlib.Path.read_text() was introduced in Python 3.5. self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
with path.open('r', encoding='utf-8') as file:
text = file.read()
self.assertEqual('Hello, UTF-8 world!\n', text)
class PathDiskTests(PathTests, unittest.TestCase): class PathDiskTests(PathTests, unittest.TestCase):

View file

@ -18,7 +18,7 @@ class CommonTextTests(util.CommonTests, unittest.TestCase):
class ReadTests: class ReadTests:
def test_read_bytes(self): def test_read_bytes(self):
result = resources.files(self.data).joinpath('binary.file').read_bytes() 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): def test_read_text_default_encoding(self):
result = ( result = (
@ -57,17 +57,15 @@ class ReadDiskTests(ReadTests, unittest.TestCase):
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
def test_read_submodule_resource(self): 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() 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): def test_read_submodule_resource_by_name(self):
result = ( result = (
resources.files('ziptestdata.subdirectory') resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
.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): class ReadNamespaceTests(ReadTests, unittest.TestCase):
@ -77,5 +75,22 @@ class ReadNamespaceTests(ReadTests, unittest.TestCase):
self.data = namespacedata01 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -10,8 +10,7 @@ from importlib.readers import MultiplexedPath, NamespaceReader
class MultiplexedPathTest(unittest.TestCase): class MultiplexedPathTest(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
path = pathlib.Path(__file__).parent / 'namespacedata01' cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
cls.folder = str(path)
def test_init_no_paths(self): def test_init_no_paths(self):
with self.assertRaises(FileNotFoundError): with self.assertRaises(FileNotFoundError):
@ -19,7 +18,7 @@ class MultiplexedPathTest(unittest.TestCase):
def test_init_file(self): def test_init_file(self):
with self.assertRaises(NotADirectoryError): with self.assertRaises(NotADirectoryError):
MultiplexedPath(os.path.join(self.folder, 'binary.file')) MultiplexedPath(self.folder / 'binary.file')
def test_iterdir(self): def test_iterdir(self):
contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
@ -27,10 +26,12 @@ class MultiplexedPathTest(unittest.TestCase):
contents.remove('__pycache__') contents.remove('__pycache__')
except (KeyError, ValueError): except (KeyError, ValueError):
pass 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): def test_iterdir_duplicate(self):
data01 = os.path.abspath(os.path.join(__file__, '..', 'data01')) data01 = pathlib.Path(__file__).parent.joinpath('data01')
contents = { contents = {
path.name for path in MultiplexedPath(self.folder, data01).iterdir() path.name for path in MultiplexedPath(self.folder, data01).iterdir()
} }
@ -60,17 +61,17 @@ class MultiplexedPathTest(unittest.TestCase):
path.open() path.open()
def test_join_path(self): def test_join_path(self):
prefix = os.path.abspath(os.path.join(__file__, '..')) data01 = pathlib.Path(__file__).parent.joinpath('data01')
data01 = os.path.join(prefix, 'data01') prefix = str(data01.parent)
path = MultiplexedPath(self.folder, data01) path = MultiplexedPath(self.folder, data01)
self.assertEqual( self.assertEqual(
str(path.joinpath('binary.file'))[len(prefix) + 1 :], str(path.joinpath('binary.file'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'binary.file'), os.path.join('namespacedata01', 'binary.file'),
) )
self.assertEqual( sub = path.joinpath('subdirectory')
str(path.joinpath('subdirectory'))[len(prefix) + 1 :], assert isinstance(sub, MultiplexedPath)
os.path.join('data01', 'subdirectory'), assert 'namespacedata01' in str(sub)
) assert 'data01' in str(sub)
self.assertEqual( self.assertEqual(
str(path.joinpath('imaginary'))[len(prefix) + 1 :], str(path.joinpath('imaginary'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'imaginary'), os.path.join('namespacedata01', 'imaginary'),
@ -82,9 +83,9 @@ class MultiplexedPathTest(unittest.TestCase):
assert not path.joinpath('imaginary/foo.py').exists() assert not path.joinpath('imaginary/foo.py').exists()
def test_join_path_common_subdir(self): def test_join_path_common_subdir(self):
prefix = os.path.abspath(os.path.join(__file__, '..')) data01 = pathlib.Path(__file__).parent.joinpath('data01')
data01 = os.path.join(prefix, 'data01') data02 = pathlib.Path(__file__).parent.joinpath('data02')
data02 = os.path.join(prefix, 'data02') prefix = str(data01.parent)
path = MultiplexedPath(data01, data02) path = MultiplexedPath(data01, data02)
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath) self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
self.assertEqual( self.assertEqual(

View file

@ -1,15 +1,10 @@
import contextlib
import sys import sys
import unittest import unittest
import uuid
import pathlib import pathlib
from . import data01 from . import data01
from . import zipdata01, zipdata02
from . import util from . import util
from importlib import resources, import_module from importlib import resources, import_module
from test.support import import_helper, os_helper
from test.support.os_helper import unlink
class ResourceTests: class ResourceTests:
@ -89,34 +84,32 @@ class ResourceCornerCaseTests(unittest.TestCase):
class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase): class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
ZIP_MODULE = zipdata01 # type: ignore ZIP_MODULE = 'data01'
def test_is_submodule_resource(self): 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()) self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
def test_read_submodule_resource_by_name(self): def test_read_submodule_resource_by_name(self):
self.assertTrue( self.assertTrue(
resources.files('ziptestdata.subdirectory') resources.files('data01.subdirectory').joinpath('binary.file').is_file()
.joinpath('binary.file')
.is_file()
) )
def test_submodule_contents(self): def test_submodule_contents(self):
submodule = import_module('ziptestdata.subdirectory') submodule = import_module('data01.subdirectory')
self.assertEqual( self.assertEqual(
names(resources.files(submodule)), {'__init__.py', 'binary.file'} names(resources.files(submodule)), {'__init__.py', 'binary.file'}
) )
def test_submodule_contents_by_name(self): def test_submodule_contents_by_name(self):
self.assertEqual( self.assertEqual(
names(resources.files('ziptestdata.subdirectory')), names(resources.files('data01.subdirectory')),
{'__init__.py', 'binary.file'}, {'__init__.py', 'binary.file'},
) )
def test_as_file_directory(self): def test_as_file_directory(self):
with resources.as_file(resources.files('ziptestdata')) as data: with resources.as_file(resources.files('data01')) as data:
assert data.name == 'ziptestdata' assert data.name == 'data01'
assert data.is_dir() assert data.is_dir()
assert data.joinpath('subdirectory').is_dir() assert data.joinpath('subdirectory').is_dir()
assert len(list(data.iterdir())) assert len(list(data.iterdir()))
@ -124,7 +117,7 @@ class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase): class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
ZIP_MODULE = zipdata02 # type: ignore ZIP_MODULE = 'data02'
def test_unrelated_contents(self): def test_unrelated_contents(self):
""" """
@ -132,93 +125,48 @@ class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
distinct resources. Ref python/importlib_resources#44. distinct resources. Ref python/importlib_resources#44.
""" """
self.assertEqual( self.assertEqual(
names(resources.files('ziptestdata.one')), names(resources.files('data02.one')),
{'__init__.py', 'resource1.txt'}, {'__init__.py', 'resource1.txt'},
) )
self.assertEqual( self.assertEqual(
names(resources.files('ziptestdata.two')), names(resources.files('data02.two')),
{'__init__.py', 'resource2.txt'}, {'__init__.py', 'resource2.txt'},
) )
@contextlib.contextmanager class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase):
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):
"""Having accessed resources in a zip file should not keep an open """Having accessed resources in a zip file should not keep an open
reference to the zip. 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): 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): 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): 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.") @unittest.skip("Desired but not supported.")
def test_as_file_does_not_keep_open(self): # pragma: no cover 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): def test_entered_path_does_not_keep_open(self):
""" """
Mimic what certifi does on import to make its bundle Mimic what certifi does on import to make its bundle
available for the process duration. 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): 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): def test_read_text_does_not_keep_open(self):
resources.files('ziptestdata').joinpath('utf-8.file').read_text( resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8')
encoding='utf-8'
)
class ResourceFromNamespaceTest01(unittest.TestCase): class ResourceFromNamespaceTests:
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)
def test_is_submodule_resource(self): def test_is_submodule_resource(self):
self.assertTrue( self.assertTrue(
resources.files(import_module('namespacedata01')) resources.files(import_module('namespacedata01'))
@ -237,7 +185,9 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
contents.remove('__pycache__') contents.remove('__pycache__')
except KeyError: except KeyError:
pass 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): def test_submodule_contents_by_name(self):
contents = names(resources.files('namespacedata01')) contents = names(resources.files('namespacedata01'))
@ -245,7 +195,45 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
contents.remove('__pycache__') contents.remove('__pycache__')
except KeyError: except KeyError:
pass 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__': if __name__ == '__main__':

View file

@ -4,11 +4,12 @@ import io
import sys import sys
import types import types
import pathlib import pathlib
import contextlib
from . import data01 from . import data01
from . import zipdata01
from importlib.resources.abc import ResourceReader 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 from importlib.machinery import ModuleSpec
@ -141,39 +142,23 @@ class CommonTests(metaclass=abc.ABCMeta):
class ZipSetupBase: class ZipSetupBase:
ZIP_MODULE = None ZIP_MODULE = 'data01'
@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
def setUp(self): def setUp(self):
modules = import_helper.modules_setup() self.fixtures = contextlib.ExitStack()
self.addCleanup(import_helper.modules_cleanup, *modules) 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): class ZipSetup(ZipSetupBase):
ZIP_MODULE = zipdata01 # type: ignore pass

View file

@ -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

View file

@ -2436,6 +2436,7 @@ TESTSUBDIRS= idlelib/idle_test \
test/test_importlib/resources/data03/namespace/portion1 \ test/test_importlib/resources/data03/namespace/portion1 \
test/test_importlib/resources/data03/namespace/portion2 \ test/test_importlib/resources/data03/namespace/portion2 \
test/test_importlib/resources/namespacedata01 \ test/test_importlib/resources/namespacedata01 \
test/test_importlib/resources/namespacedata01/subdirectory \
test/test_importlib/resources/zipdata01 \ test/test_importlib/resources/zipdata01 \
test/test_importlib/resources/zipdata02 \ test/test_importlib/resources/zipdata02 \
test/test_importlib/source \ test/test_importlib/source \

View file

@ -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.