diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2021-03-04 13:43:00 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-04 13:43:00 -0500 |
commit | 67148254146948041a77d8a2989f41b88cdb2f99 (patch) | |
tree | 036bcb818e80090b34f0c59f57f8b6946b52b21d /Lib/test/test_importlib | |
parent | fbf75b9997e280b1220755d0a17dbed71240d42e (diff) | |
download | cpython-git-67148254146948041a77d8a2989f41b88cdb2f99.tar.gz |
bpo-42129: Add support for resources in namespaces (GH-24670)
* Unify behavior in ResourceReaderDefaultsTests and align with the behavior found in importlib_resources.
* Equip NamespaceLoader with a NamespaceReader.
* Apply changes from importlib_resources 5.0.4
Diffstat (limited to 'Lib/test/test_importlib')
-rw-r--r-- | Lib/test/test_importlib/namespacedata01/binary.file | bin | 0 -> 4 bytes | |||
-rw-r--r-- | Lib/test/test_importlib/namespacedata01/utf-16.file | bin | 0 -> 44 bytes | |||
-rw-r--r-- | Lib/test/test_importlib/namespacedata01/utf-8.file | 1 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_abc.py | 4 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_files.py | 2 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_open.py | 27 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_path.py | 12 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_read.py | 15 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_reader.py | 123 | ||||
-rw-r--r-- | Lib/test/test_importlib/test_resource.py | 169 | ||||
-rwxr-xr-x | Lib/test/test_importlib/update-zips.py | 53 | ||||
-rw-r--r-- | Lib/test/test_importlib/zipdata01/ziptestdata.zip | bin | 1131 -> 876 bytes | |||
-rw-r--r-- | Lib/test/test_importlib/zipdata02/ziptestdata.zip | bin | 698 -> 698 bytes |
13 files changed, 304 insertions, 102 deletions
diff --git a/Lib/test/test_importlib/namespacedata01/binary.file b/Lib/test/test_importlib/namespacedata01/binary.file Binary files differnew file mode 100644 index 0000000000..eaf36c1dac --- /dev/null +++ b/Lib/test/test_importlib/namespacedata01/binary.file diff --git a/Lib/test/test_importlib/namespacedata01/utf-16.file b/Lib/test/test_importlib/namespacedata01/utf-16.file Binary files differnew file mode 100644 index 0000000000..2cb772295e --- /dev/null +++ b/Lib/test/test_importlib/namespacedata01/utf-16.file diff --git a/Lib/test/test_importlib/namespacedata01/utf-8.file b/Lib/test/test_importlib/namespacedata01/utf-8.file new file mode 100644 index 0000000000..1c0132ad90 --- /dev/null +++ b/Lib/test/test_importlib/namespacedata01/utf-8.file @@ -0,0 +1 @@ +Hello, UTF-8 world! diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py index d8b9fc89f2..d1c89c183b 100644 --- a/Lib/test/test_importlib/test_abc.py +++ b/Lib/test/test_importlib/test_abc.py @@ -338,7 +338,9 @@ class ResourceReaderDefaultsTests(ABCTestHarness): self.ins.is_resource('dummy_file') def test_contents(self): - self.assertEqual([], list(self.ins.contents())) + with self.assertRaises(FileNotFoundError): + self.ins.contents() + (Frozen_RRDefaultTests, Source_RRDefaultsTests diff --git a/Lib/test/test_importlib/test_files.py b/Lib/test/test_importlib/test_files.py index fa7af82bf0..1e7a1f3cbc 100644 --- a/Lib/test/test_importlib/test_files.py +++ b/Lib/test/test_importlib/test_files.py @@ -21,7 +21,7 @@ class FilesTests: @unittest.skipUnless( hasattr(typing, 'runtime_checkable'), "Only suitable when typing supports runtime_checkable", - ) + ) def test_traversable(self): assert isinstance(resources.files(self.data), Traversable) diff --git a/Lib/test/test_importlib/test_open.py b/Lib/test/test_importlib/test_open.py index fd6e84b70f..b75675f43b 100644 --- a/Lib/test/test_importlib/test_open.py +++ b/Lib/test/test_importlib/test_open.py @@ -29,34 +29,32 @@ class OpenTests: self.assertEqual(result, 'Hello, UTF-8 world!\n') def test_open_text_given_encoding(self): - with resources.open_text( - self.data, 'utf-16.file', 'utf-16', 'strict') as fp: + with resources.open_text(self.data, 'utf-16.file', 'utf-16', 'strict') as fp: result = fp.read() self.assertEqual(result, 'Hello, UTF-16 world!\n') def test_open_text_with_errors(self): # Raises UnicodeError without the 'errors' argument. - with resources.open_text( - self.data, 'utf-16.file', 'utf-8', 'strict') as fp: + with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'strict') as fp: self.assertRaises(UnicodeError, fp.read) - with resources.open_text( - self.data, 'utf-16.file', 'utf-8', 'ignore') as fp: + with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'ignore') as fp: result = fp.read() self.assertEqual( result, 'H\x00e\x00l\x00l\x00o\x00,\x00 ' '\x00U\x00T\x00F\x00-\x001\x006\x00 ' - '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00') + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) def test_open_binary_FileNotFoundError(self): self.assertRaises( - FileNotFoundError, - resources.open_binary, self.data, 'does-not-exist') + FileNotFoundError, resources.open_binary, self.data, 'does-not-exist' + ) def test_open_text_FileNotFoundError(self): self.assertRaises( - FileNotFoundError, - resources.open_text, self.data, 'does-not-exist') + FileNotFoundError, resources.open_text, self.data, 'does-not-exist' + ) class OpenDiskTests(OpenTests, unittest.TestCase): @@ -64,6 +62,13 @@ class OpenDiskTests(OpenTests, unittest.TestCase): self.data = data01 +class OpenDiskNamespaceTests(OpenTests, unittest.TestCase): + def setUp(self): + from . import namespacedata01 + + self.data = namespacedata01 + + class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): pass diff --git a/Lib/test/test_importlib/test_path.py b/Lib/test/test_importlib/test_path.py index abf8086558..2110770e2a 100644 --- a/Lib/test/test_importlib/test_path.py +++ b/Lib/test/test_importlib/test_path.py @@ -1,3 +1,4 @@ +import io import unittest from importlib import resources @@ -37,6 +38,17 @@ class PathDiskTests(PathTests, unittest.TestCase): assert 'data' in str(path) +class PathMemoryTests(PathTests, unittest.TestCase): + def setUp(self): + file = io.BytesIO(b'Hello, UTF-8 world!\n') + self.addCleanup(file.close) + self.data = util.create_package( + file=file, path=FileNotFoundError("package exists only in memory") + ) + self.data.__spec__.origin = None + self.data.__spec__.has_location = False + + class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase): def test_remove_in_context_manager(self): # It is not an error if the file that was temporarily stashed on the diff --git a/Lib/test/test_importlib/test_read.py b/Lib/test/test_importlib/test_read.py index ff78d0b294..f6ec13af62 100644 --- a/Lib/test/test_importlib/test_read.py +++ b/Lib/test/test_importlib/test_read.py @@ -25,20 +25,19 @@ class ReadTests: self.assertEqual(result, 'Hello, UTF-8 world!\n') def test_read_text_given_encoding(self): - result = resources.read_text( - self.data, 'utf-16.file', encoding='utf-16') + result = resources.read_text(self.data, 'utf-16.file', encoding='utf-16') self.assertEqual(result, 'Hello, UTF-16 world!\n') def test_read_text_with_errors(self): # Raises UnicodeError without the 'errors' argument. - self.assertRaises( - UnicodeError, resources.read_text, self.data, 'utf-16.file') + self.assertRaises(UnicodeError, resources.read_text, self.data, 'utf-16.file') result = resources.read_text(self.data, 'utf-16.file', errors='ignore') self.assertEqual( result, 'H\x00e\x00l\x00l\x00o\x00,\x00 ' '\x00U\x00T\x00F\x00-\x001\x006\x00 ' - '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00') + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) class ReadDiskTests(ReadTests, unittest.TestCase): @@ -48,13 +47,11 @@ class ReadDiskTests(ReadTests, unittest.TestCase): class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): def test_read_submodule_resource(self): submodule = import_module('ziptestdata.subdirectory') - result = resources.read_binary( - submodule, 'binary.file') + result = resources.read_binary(submodule, 'binary.file') self.assertEqual(result, b'\0\1\2\3') def test_read_submodule_resource_by_name(self): - result = resources.read_binary( - 'ziptestdata.subdirectory', 'binary.file') + result = resources.read_binary('ziptestdata.subdirectory', 'binary.file') self.assertEqual(result, b'\0\1\2\3') diff --git a/Lib/test/test_importlib/test_reader.py b/Lib/test/test_importlib/test_reader.py new file mode 100644 index 0000000000..905d4fcdec --- /dev/null +++ b/Lib/test/test_importlib/test_reader.py @@ -0,0 +1,123 @@ +import os.path +import sys +import pathlib +import unittest + +from importlib import import_module +from importlib.readers import MultiplexedPath, NamespaceReader + + +class MultiplexedPathTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + path = pathlib.Path(__file__).parent / 'namespacedata01' + cls.folder = str(path) + + def test_init_no_paths(self): + with self.assertRaises(FileNotFoundError): + MultiplexedPath() + + def test_init_file(self): + with self.assertRaises(NotADirectoryError): + MultiplexedPath(os.path.join(self.folder, 'binary.file')) + + def test_iterdir(self): + contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} + try: + contents.remove('__pycache__') + except (KeyError, ValueError): + pass + self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'}) + + def test_iterdir_duplicate(self): + data01 = os.path.abspath(os.path.join(__file__, '..', 'data01')) + contents = { + path.name for path in MultiplexedPath(self.folder, data01).iterdir() + } + for remove in ('__pycache__', '__init__.pyc'): + try: + contents.remove(remove) + except (KeyError, ValueError): + pass + self.assertEqual( + contents, + {'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'}, + ) + + def test_is_dir(self): + self.assertEqual(MultiplexedPath(self.folder).is_dir(), True) + + def test_is_file(self): + self.assertEqual(MultiplexedPath(self.folder).is_file(), False) + + def test_open_file(self): + path = MultiplexedPath(self.folder) + with self.assertRaises(FileNotFoundError): + path.read_bytes() + with self.assertRaises(FileNotFoundError): + path.read_text() + with self.assertRaises(FileNotFoundError): + path.open() + + def test_join_path(self): + print('test_join_path') + prefix = os.path.abspath(os.path.join(__file__, '..')) + data01 = os.path.join(prefix, 'data01') + 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'), + ) + self.assertEqual( + str(path.joinpath('imaginary'))[len(prefix) + 1 :], + os.path.join('namespacedata01', 'imaginary'), + ) + + def test_repr(self): + self.assertEqual( + repr(MultiplexedPath(self.folder)), + "MultiplexedPath('{}')".format(self.folder), + ) + + +class NamespaceReaderTest(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) + + def test_init_error(self): + with self.assertRaises(ValueError): + NamespaceReader(['path1', 'path2']) + + def test_resource_path(self): + namespacedata01 = import_module('namespacedata01') + reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) + + root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) + self.assertEqual( + reader.resource_path('binary.file'), os.path.join(root, 'binary.file') + ) + self.assertEqual( + reader.resource_path('imaginary'), os.path.join(root, 'imaginary') + ) + + def test_files(self): + namespacedata01 = import_module('namespacedata01') + reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) + root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) + self.assertIsInstance(reader.files(), MultiplexedPath) + self.assertEqual(repr(reader.files()), "MultiplexedPath('{}')".format(root)) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_importlib/test_resource.py b/Lib/test/test_importlib/test_resource.py index 1d1bdad1b2..f8d861e9d4 100644 --- a/Lib/test/test_importlib/test_resource.py +++ b/Lib/test/test_importlib/test_resource.py @@ -27,20 +27,21 @@ class ResourceTests: def test_contents(self): contents = set(resources.contents(self.data)) # There may be cruft in the directory listing of the data directory. - # Under Python 3 we could have a __pycache__ directory, and under - # Python 2 we could have .pyc files. These are both artifacts of the - # test suite importing these modules and writing these caches. They - # aren't germane to this test, so just filter them out. + # It could have a __pycache__ directory, + # an artifact of the + # test suite importing these modules, which + # are not germane to this test, so just filter them out. contents.discard('__pycache__') - contents.discard('__init__.pyc') - contents.discard('__init__.pyo') - self.assertEqual(contents, { - '__init__.py', - 'subdirectory', - 'utf-8.file', - 'binary.file', - 'utf-16.file', - }) + self.assertEqual( + contents, + { + '__init__.py', + 'subdirectory', + 'utf-8.file', + 'binary.file', + 'utf-16.file', + }, + ) class ResourceDiskTests(ResourceTests, unittest.TestCase): @@ -55,27 +56,26 @@ class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): class ResourceLoaderTests(unittest.TestCase): def test_resource_contents(self): package = util.create_package( - file=data01, path=data01.__file__, contents=['A', 'B', 'C']) - self.assertEqual( - set(resources.contents(package)), - {'A', 'B', 'C'}) + file=data01, path=data01.__file__, contents=['A', 'B', 'C'] + ) + self.assertEqual(set(resources.contents(package)), {'A', 'B', 'C'}) def test_resource_is_resource(self): package = util.create_package( - file=data01, path=data01.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F']) + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) self.assertTrue(resources.is_resource(package, 'B')) def test_resource_directory_is_not_resource(self): package = util.create_package( - file=data01, path=data01.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F']) + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) self.assertFalse(resources.is_resource(package, 'D')) def test_resource_missing_is_not_resource(self): package = util.create_package( - file=data01, path=data01.__file__, - contents=['A', 'B', 'C', 'D/E', 'D/F']) + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) self.assertFalse(resources.is_resource(package, 'Z')) @@ -86,90 +86,63 @@ class ResourceCornerCaseTests(unittest.TestCase): # 2. Are not on the file system # 3. Are not in a zip file module = util.create_package( - file=data01, path=data01.__file__, contents=['A', 'B', 'C']) + file=data01, path=data01.__file__, contents=['A', 'B', 'C'] + ) # Give the module a dummy loader. module.__loader__ = object() # Give the module a dummy origin. module.__file__ = '/path/which/shall/not/be/named' - if sys.version_info >= (3,): - module.__spec__.loader = module.__loader__ - module.__spec__.origin = module.__file__ + module.__spec__.loader = module.__loader__ + module.__spec__.origin = module.__file__ self.assertFalse(resources.is_resource(module, 'A')) -class ResourceFromZipsTest(util.ZipSetupBase, unittest.TestCase): - ZIP_MODULE = zipdata02 # type: ignore - - def test_unrelated_contents(self): - # https://gitlab.com/python-devs/importlib_resources/issues/44 - # - # Here we have a zip file with two unrelated subpackages. The bug - # reports that getting the contents of a resource returns unrelated - # files. - self.assertEqual( - set(resources.contents('ziptestdata.one')), - {'__init__.py', 'resource1.txt'}) - self.assertEqual( - set(resources.contents('ziptestdata.two')), - {'__init__.py', 'resource2.txt'}) - - -class SubdirectoryResourceFromZipsTest(util.ZipSetupBase, unittest.TestCase): - ZIP_MODULE = zipdata01 # type: ignore +class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase): + ZIP_MODULE = zipdata01 # type: ignore def test_is_submodule_resource(self): submodule = import_module('ziptestdata.subdirectory') - self.assertTrue( - resources.is_resource(submodule, 'binary.file')) + self.assertTrue(resources.is_resource(submodule, 'binary.file')) def test_read_submodule_resource_by_name(self): self.assertTrue( - resources.is_resource('ziptestdata.subdirectory', 'binary.file')) + resources.is_resource('ziptestdata.subdirectory', 'binary.file') + ) def test_submodule_contents(self): submodule = import_module('ziptestdata.subdirectory') self.assertEqual( - set(resources.contents(submodule)), - {'__init__.py', 'binary.file'}) + set(resources.contents(submodule)), {'__init__.py', 'binary.file'} + ) def test_submodule_contents_by_name(self): self.assertEqual( set(resources.contents('ziptestdata.subdirectory')), - {'__init__.py', 'binary.file'}) - - -class NamespaceTest(unittest.TestCase): - def test_namespaces_cannot_have_resources(self): - contents = resources.contents('test.test_importlib.data03.namespace') - self.assertFalse(list(contents)) - # Even though there is a file in the namespace directory, it is not - # considered a resource, since namespace packages can't have them. - self.assertFalse(resources.is_resource( - 'test.test_importlib.data03.namespace', - 'resource1.txt')) - # We should get an exception if we try to read it or open it. - self.assertRaises( - FileNotFoundError, - resources.open_text, - 'test.test_importlib.data03.namespace', 'resource1.txt') - self.assertRaises( - FileNotFoundError, - resources.open_binary, - 'test.test_importlib.data03.namespace', 'resource1.txt') - self.assertRaises( - FileNotFoundError, - resources.read_text, - 'test.test_importlib.data03.namespace', 'resource1.txt') - self.assertRaises( - FileNotFoundError, - resources.read_binary, - 'test.test_importlib.data03.namespace', 'resource1.txt') + {'__init__.py', 'binary.file'}, + ) + + +class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase): + ZIP_MODULE = zipdata02 # type: ignore + + def test_unrelated_contents(self): + """ + Test thata zip with two unrelated subpackages return + distinct resources. Ref python/importlib_resources#44. + """ + self.assertEqual( + set(resources.contents('ziptestdata.one')), {'__init__.py', 'resource1.txt'} + ) + self.assertEqual( + set(resources.contents('ziptestdata.two')), {'__init__.py', 'resource2.txt'} + ) class DeletingZipsTest(unittest.TestCase): """Having accessed resources in a zip file should not keep an open reference to the zip. """ + ZIP_MODULE = zipdata01 def setUp(self): @@ -241,5 +214,41 @@ class DeletingZipsTest(unittest.TestCase): del c +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) + + def test_is_submodule_resource(self): + self.assertTrue( + resources.is_resource(import_module('namespacedata01'), 'binary.file') + ) + + def test_read_submodule_resource_by_name(self): + self.assertTrue(resources.is_resource('namespacedata01', 'binary.file')) + + def test_submodule_contents(self): + contents = set(resources.contents(import_module('namespacedata01'))) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'}) + + def test_submodule_contents_by_name(self): + contents = set(resources.contents('namespacedata01')) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'}) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_importlib/update-zips.py b/Lib/test/test_importlib/update-zips.py new file mode 100755 index 0000000000..9ef0224ca6 --- /dev/null +++ b/Lib/test/test_importlib/update-zips.py @@ -0,0 +1,53 @@ +""" +Generate the zip test data files. + +Run to build the tests/zipdataNN/ziptestdata.zip files from +files in tests/dataNN. + +Replaces the file with the working copy, but does commit anything +to the source repo. +""" + +import contextlib +import os +import pathlib +import zipfile + + +def main(): + """ + >>> from unittest import mock + >>> monkeypatch = getfixture('monkeypatch') + >>> monkeypatch.setattr(zipfile, 'ZipFile', mock.MagicMock()) + >>> print(); main() # print workaround for bpo-32509 + <BLANKLINE> + ...data01... -> ziptestdata/... + ... + ...data02... -> ziptestdata/... + ... + """ + suffixes = '01', '02' + tuple(map(generate, suffixes)) + + +def generate(suffix): + root = pathlib.Path(__file__).parent.relative_to(os.getcwd()) + zfpath = root / f'zipdata{suffix}/ziptestdata.zip' + with zipfile.ZipFile(zfpath, 'w') as zf: + for src, rel in walk(root / f'data{suffix}'): + dst = 'ziptestdata' / pathlib.PurePosixPath(rel.as_posix()) + print(src, '->', dst) + zf.write(src, dst) + + +def walk(datapath): + for dirpath, dirnames, filenames in os.walk(datapath): + with contextlib.suppress(KeyError): + dirnames.remove('__pycache__') + for filename in filenames: + res = pathlib.Path(dirpath) / filename + rel = res.relative_to(datapath) + yield res, rel + + +__name__ == '__main__' and main() diff --git a/Lib/test/test_importlib/zipdata01/ziptestdata.zip b/Lib/test/test_importlib/zipdata01/ziptestdata.zip Binary files differindex 12f7872cd5..9a3bb0739f 100644 --- a/Lib/test/test_importlib/zipdata01/ziptestdata.zip +++ b/Lib/test/test_importlib/zipdata01/ziptestdata.zip diff --git a/Lib/test/test_importlib/zipdata02/ziptestdata.zip b/Lib/test/test_importlib/zipdata02/ziptestdata.zip Binary files differindex 9ee00586e7..d63ff512d2 100644 --- a/Lib/test/test_importlib/zipdata02/ziptestdata.zip +++ b/Lib/test/test_importlib/zipdata02/ziptestdata.zip |