From e82b5675b9fef7dd971b796136687f915f7a39ca Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 4 Dec 2017 20:54:10 -0500 Subject: [PATCH] Add is_resource() and contents() (#37) Support for is_resource() and contents() --- importlib_resources/__init__.py | 6 +- importlib_resources/_py2.py | 114 ++++++++++++ importlib_resources/_py3.py | 170 +++++++++++++++--- importlib_resources/abc.py | 17 +- importlib_resources/docs/api.rst | 32 +++- importlib_resources/docs/migration.rst | 53 ++++++ .../tests/data/subdirectory/__init__.py | 0 .../tests/data/subdirectory/binary.file | Bin 0 -> 4 bytes .../tests/data/ziptestdata.zip | Bin 576 -> 0 bytes importlib_resources/tests/test_resource.py | 97 ++++++++++ importlib_resources/tests/util.py | 23 ++- importlib_resources/tests/zipdata/__init__.py | 0 .../tests/zipdata/ziptestdata.zip | Bin 0 -> 876 bytes update-tests.py | 34 ++-- 14 files changed, 492 insertions(+), 54 deletions(-) create mode 100644 importlib_resources/tests/data/subdirectory/__init__.py create mode 100644 importlib_resources/tests/data/subdirectory/binary.file delete mode 100644 importlib_resources/tests/data/ziptestdata.zip create mode 100644 importlib_resources/tests/test_resource.py create mode 100644 importlib_resources/tests/zipdata/__init__.py create mode 100644 importlib_resources/tests/zipdata/ziptestdata.zip diff --git a/importlib_resources/__init__.py b/importlib_resources/__init__.py index c8e6ac55d36448e..d00047a34baf18e 100644 --- a/importlib_resources/__init__.py +++ b/importlib_resources/__init__.py @@ -6,6 +6,8 @@ if sys.version_info >= (3,): - from importlib_resources._py3 import open, path, read + from importlib_resources._py3 import ( + contents, is_resource, open, path, read) else: - from importlib_resources._py2 import open, path, read + from importlib_resources._py2 import ( + contents, is_resource, open, path, read) diff --git a/importlib_resources/_py2.py b/importlib_resources/_py2.py index 6646940058da6d1..53a290ca5ec33fd 100644 --- a/importlib_resources/_py2.py +++ b/importlib_resources/_py2.py @@ -1,4 +1,5 @@ import os +import errno import tempfile from ._compat import FileNotFoundError @@ -7,6 +8,7 @@ from importlib import import_module from io import BytesIO, open as io_open from pathlib2 import Path +from zipfile import ZipFile def _get_package(package): @@ -120,3 +122,115 @@ def path(package, file_name): os.remove(raw_path) except FileNotFoundError: pass + + +def is_resource(package, file_name): + """True if file_name is a resource inside package. + + Directories are *not* resources. + """ + package = _get_package(package) + _normalize_path(file_name) + try: + package_contents = set(contents(package)) + except OSError as error: + if error.errno not in (errno.ENOENT, errno.ENOTDIR): + # We won't hit this in the Python 2 tests, so it'll appear + # uncovered. We could mock os.listdir() to return a non-ENOENT or + # ENOTDIR, but then we'd have to depend on another external + # library since Python 2 doesn't have unittest.mock. It's not + # worth it. + raise # pragma: ge3 + return False + if file_name not in package_contents: + return False + # Just because the given file_name lives as an entry in the package's + # contents doesn't necessarily mean it's a resource. Directories are not + # resources, so let's try to find out if it's a directory or not. + path = Path(package.__file__).parent / file_name + if path.is_file(): + return True + if path.is_dir(): + return False + # If it's not a file and it's not a directory, what is it? Well, this + # means the file doesn't exist on the file system, so it probably lives + # inside a zip file. We have to crack open the zip, look at its table of + # contents, and make sure that this entry doesn't have sub-entries. + archive_path = package.__loader__.archive # type: ignore + package_directory = Path(package.__file__).parent + with ZipFile(archive_path) as zf: + toc = zf.namelist() + relpath = package_directory.relative_to(archive_path) + candidate_path = relpath / file_name + for entry in toc: # pragma: nobranch + try: + relative_to_candidate = Path(entry).relative_to(candidate_path) + except ValueError: + # The two paths aren't relative to each other so we can ignore it. + continue + # Since directories aren't explicitly listed in the zip file, we must + # infer their 'directory-ness' by looking at the number of path + # components in the path relative to the package resource we're + # looking up. If there are zero additional parts, it's a file, i.e. a + # resource. If there are more than zero it's a directory, i.e. not a + # resource. It has to be one of these two cases. + return len(relative_to_candidate.parts) == 0 + # I think it's impossible to get here. It would mean that we are looking + # for a resource in a zip file, there's an entry matching it in the return + # value of contents(), but we never actually found it in the zip's table of + # contents. + raise AssertionError('Impossible situation') + + +def contents(package): + """Return the list of entries in package. + + Note that not all entries are resources. Specifically, directories are + not considered resources. Use `is_resource()` on each entry returned here + to check if it is a resource or not. + """ + package = _get_package(package) + package_directory = Path(package.__file__).parent + try: + # Python 2 doesn't support `yield from`. We fall back to using + # os.listdir() here to simplify the returning of just the name. + for entry in os.listdir(str(package_directory)): + yield entry + except OSError as error: + if error.errno not in (errno.ENOENT, errno.ENOTDIR): + # We won't hit this in the Python 2 tests, so it'll appear + # uncovered. We could mock os.listdir() to return a non-ENOENT or + # ENOTDIR, but then we'd have to depend on another external + # library since Python 2 doesn't have unittest.mock. It's not + # worth it. + raise # pragma: ge3 + # The package is probably in a zip file. + archive_path = getattr(package.__loader__, 'archive', None) + if archive_path is None: + raise + relpath = package_directory.relative_to(archive_path) + with ZipFile(archive_path) as zf: + toc = zf.namelist() + subdirs_seen = set() # type: Set + for filename in toc: + path = Path(filename) + # Strip off any path component parts that are in common with the + # package directory, relative to the zip archive's file system + # path. This gives us all the parts that live under the named + # package inside the zip file. If the length of these subparts is + # exactly 1, then it is situated inside the package. The resulting + # length will be 0 if it's above the package, and it will be + # greater than 1 if it lives in a subdirectory of the package + # directory. + # + # However, since directories themselves don't appear in the zip + # archive as a separate entry, we need to return the first path + # component for any case that has > 1 subparts -- but only once! + subparts = path.parts[len(relpath.parts):] + if len(subparts) == 1: + yield subparts[0] + elif len(subparts) > 1: # pragma: nobranch + subdir = subparts[0] + if subdir not in subdirs_seen: + subdirs_seen.add(subdir) + yield subdir diff --git a/importlib_resources/_py3.py b/importlib_resources/_py3.py index 4b8ef80afb26ac3..8e7d1476c8c68ba 100644 --- a/importlib_resources/_py3.py +++ b/importlib_resources/_py3.py @@ -11,9 +11,10 @@ from io import BytesIO, TextIOWrapper from pathlib import Path from types import ModuleType -from typing import Iterator, Union +from typing import Iterator, Optional, Set, Union # noqa: F401 from typing import cast from typing.io import IO +from zipfile import ZipFile Package = Union[ModuleType, str] @@ -47,6 +48,18 @@ def _normalize_path(path) -> str: return file_name +def _get_resource_reader( + package: ModuleType) -> Optional[resources_abc.ResourceReader]: + # Return the package's loader if it's a ResourceReader. We can't use + # a issubclass() check here because apparently abc.'s __subclasscheck__() + # hook wants to create a weak reference to the object, but + # zipimport.zipimporter does not support weak references, resulting in a + # TypeError. That seems terrible. + if hasattr(package.__spec__.loader, 'open_resource'): + return cast(resources_abc.ResourceReader, package.__spec__.loader) + return None + + def open(package: Package, file_name: FileName, encoding: str = None, @@ -54,35 +67,34 @@ def open(package: Package, """Return a file-like object opened for reading of the resource.""" file_name = _normalize_path(file_name) package = _get_package(package) - if hasattr(package.__spec__.loader, 'open_resource'): - reader = cast(resources_abc.ResourceReader, package.__spec__.loader) + reader = _get_resource_reader(package) + if reader is not None: return _wrap_file(reader.open_resource(file_name), encoding, errors) + # Using pathlib doesn't work well here due to the lack of 'strict' + # argument for pathlib.Path.resolve() prior to Python 3.6. + absolute_package_path = os.path.abspath(package.__spec__.origin) + package_path = os.path.dirname(absolute_package_path) + full_path = os.path.join(package_path, file_name) + if encoding is None: + args = dict(mode='rb') else: - # Using pathlib doesn't work well here due to the lack of 'strict' - # argument for pathlib.Path.resolve() prior to Python 3.6. - absolute_package_path = os.path.abspath(package.__spec__.origin) - package_path = os.path.dirname(absolute_package_path) - full_path = os.path.join(package_path, file_name) - if encoding is None: - args = dict(mode='rb') - else: - args = dict(mode='r', encoding=encoding, errors=errors) + args = dict(mode='r', encoding=encoding, errors=errors) + try: + return builtins_open(full_path, **args) # type: ignore + except IOError: + # Just assume the loader is a resource loader; all the relevant + # importlib.machinery loaders are and an AttributeError for + # get_data() will make it clear what is needed from the loader. + loader = cast(ResourceLoader, package.__spec__.loader) try: - return builtins_open(full_path, **args) # type: ignore + data = loader.get_data(full_path) except IOError: - # Just assume the loader is a resource loader; all the relevant - # importlib.machinery loaders are and an AttributeError for - # get_data() will make it clear what is needed from the loader. - loader = cast(ResourceLoader, package.__spec__.loader) - try: - data = loader.get_data(full_path) - except IOError: - package_name = package.__spec__.name - message = '{!r} resource not found in {!r}'.format( - file_name, package_name) - raise FileNotFoundError(message) - else: - return _wrap_file(BytesIO(data), encoding, errors) + package_name = package.__spec__.name + message = '{!r} resource not found in {!r}'.format( + file_name, package_name) + raise FileNotFoundError(message) + else: + return _wrap_file(BytesIO(data), encoding, errors) def read(package: Package, @@ -119,8 +131,8 @@ def path(package: Package, file_name: FileName) -> Iterator[Path]: """ file_name = _normalize_path(file_name) package = _get_package(package) - if hasattr(package.__spec__.loader, 'resource_path'): - reader = cast(resources_abc.ResourceReader, package.__spec__.loader) + reader = _get_resource_reader(package) + if reader is not None: try: yield Path(reader.resource_path(file_name)) return @@ -148,3 +160,105 @@ def path(package: Package, file_name: FileName) -> Iterator[Path]: os.remove(raw_path) except FileNotFoundError: pass + + +def is_resource(package: Package, file_name: str) -> bool: + """True if file_name is a resource inside package. + + Directories are *not* resources. + """ + package = _get_package(package) + _normalize_path(file_name) + reader = _get_resource_reader(package) + if reader is not None: + return reader.is_resource(file_name) + try: + package_contents = set(contents(package)) + except (NotADirectoryError, FileNotFoundError): + return False + if file_name not in package_contents: + return False + # Just because the given file_name lives as an entry in the package's + # contents doesn't necessarily mean it's a resource. Directories are not + # resources, so let's try to find out if it's a directory or not. + path = Path(package.__spec__.origin).parent / file_name + if path.is_file(): + return True + if path.is_dir(): + return False + # If it's not a file and it's not a directory, what is it? Well, this + # means the file doesn't exist on the file system, so it probably lives + # inside a zip file. We have to crack open the zip, look at its table of + # contents, and make sure that this entry doesn't have sub-entries. + archive_path = package.__spec__.loader.archive # type: ignore + package_directory = Path(package.__spec__.origin).parent + with ZipFile(archive_path) as zf: + toc = zf.namelist() + relpath = package_directory.relative_to(archive_path) + candidate_path = relpath / file_name + for entry in toc: # pragma: nobranch + try: + relative_to_candidate = Path(entry).relative_to(candidate_path) + except ValueError: + # The two paths aren't relative to each other so we can ignore it. + continue + # Since directories aren't explicitly listed in the zip file, we must + # infer their 'directory-ness' by looking at the number of path + # components in the path relative to the package resource we're + # looking up. If there are zero additional parts, it's a file, i.e. a + # resource. If there are more than zero it's a directory, i.e. not a + # resource. It has to be one of these two cases. + return len(relative_to_candidate.parts) == 0 + # I think it's impossible to get here. It would mean that we are looking + # for a resource in a zip file, there's an entry matching it in the return + # value of contents(), but we never actually found it in the zip's table of + # contents. + raise AssertionError('Impossible situation') + + +def contents(package: Package) -> Iterator[str]: + """Return the list of entries in package. + + Note that not all entries are resources. Specifically, directories are + not considered resources. Use `is_resource()` on each entry returned here + to check if it is a resource or not. + """ + package = _get_package(package) + reader = _get_resource_reader(package) + if reader is not None: + yield from reader.contents() + return + package_directory = Path(package.__spec__.origin).parent + try: + yield from os.listdir(str(package_directory)) + except (NotADirectoryError, FileNotFoundError): + # The package is probably in a zip file. + archive_path = getattr(package.__spec__.loader, 'archive', None) + if archive_path is None: + raise + relpath = package_directory.relative_to(archive_path) + with ZipFile(archive_path) as zf: + toc = zf.namelist() + subdirs_seen = set() # type: Set + for filename in toc: + path = Path(filename) + # Strip off any path component parts that are in common with the + # package directory, relative to the zip archive's file system + # path. This gives us all the parts that live under the named + # package inside the zip file. If the length of these subparts is + # exactly 1, then it is situated inside the package. The resulting + # length will be 0 if it's above the package, and it will be + # greater than 1 if it lives in a subdirectory of the package + # directory. + # + # However, since directories themselves don't appear in the zip + # archive as a separate entry, we need to return the first path + # component for any case that has > 1 subparts -- but only once! + subparts = path.parts[len(relpath.parts):] + if len(subparts) == 1: + yield subparts[0] + elif len(subparts) > 1: # pragma: nobranch + subdir = subparts[0] + if subdir not in subdirs_seen: + subdirs_seen.add(subdir) + yield subdir diff --git a/importlib_resources/abc.py b/importlib_resources/abc.py index 6af59822d78e3eb..ada48327db69476 100644 --- a/importlib_resources/abc.py +++ b/importlib_resources/abc.py @@ -6,7 +6,7 @@ # We use mypy's comment syntax here since this file must be compatible with # both Python 2 and 3. try: - from typing import BinaryIO, Text # noqa: F401 + from typing import BinaryIO, Iterator, Text # noqa: F401 except ImportError: # Python 2 pass @@ -41,3 +41,18 @@ def resource_path(self, path): # NotImplementedError so that if this method is accidentally called, # it'll still do the right thing. raise FileNotFoundError + + @abstractmethod + def is_resource(self, path): + # type: (Text) -> bool + """Return True if the named path is a resource. + + Files are resources, directories are not. + """ + raise FileNotFoundError + + @abstractmethod + def contents(self): + # type: () -> Iterator[str] + """Return an iterator over the string contents of the resource.""" + raise FileNotFoundError diff --git a/importlib_resources/docs/api.rst b/importlib_resources/docs/api.rst index 0a9b487db4ec8e8..d08a93c08b41b94 100644 --- a/importlib_resources/docs/api.rst +++ b/importlib_resources/docs/api.rst @@ -99,9 +99,37 @@ Functions not have sub-resources (i.e. it cannot be a directory). :type file_name: ``FileName`` :returns: A context manager for use in a ``with``-statement. Entering - the context manager provides a :py:class:`pathlib.Path` - object. + the context manager provides a :py:class:`pathlib.Path` object. :rtype: context manager providing a :py:class:`pathlib.Path` object +.. py:function:: importlib_resources.is_resource(package, file_name) + + Return True if there is a resource named ``file_name`` in the package, + otherwise False. Remember that directories are *not* resources! + + :param package: A package name or module object. See above for the API + that such module objects must support. + :type package: ``Package`` + :param file_name: The name of the resource to read within ``package``. + ``file_name`` may not contain path separators and it may + not have sub-resources (i.e. it cannot be a directory). + :type file_name: ``FileName`` + :returns: A flag indicating whether the resource exists or not. + :rtype: bool + + +.. py:function:: importlib_resources.contents(package) + + Return an iterator over the contents of the package. The iterator can + return resources (e.g. files) and non-resources (e.g. directories). The + iterator does not recurse into subdirectories. + + :param package: A package name or module object. See above for the API + that such module objects must support. + :type package: ``Package`` + :returns: The contents of the package, both resources and non-resources. + :rtype: An iterator over strings. + + .. _`context manager`: https://docs.python.org/3/library/stdtypes.html#typecontextmanager diff --git a/importlib_resources/docs/migration.rst b/importlib_resources/docs/migration.rst index c7b9d3dddbb7c70..19e39d395f112f5 100644 --- a/importlib_resources/docs/migration.rst +++ b/importlib_resources/docs/migration.rst @@ -14,6 +14,8 @@ access`_ APIs: * ``pkg_resources.resource_filename()`` * ``pkg_resources.resource_stream()`` * ``pkg_resources.resource_string()`` +* ``pkg_resources.resource_listdir()`` +* ``pkg_resources.resource_isdir()`` Keep in mind that ``pkg_resources`` defines *resources* to include directories. ``importlib_resources`` does not treat directories as resources; @@ -109,4 +111,55 @@ a ``unicode`` in Python 2 or a ``str`` in Python 3, read and decoded with the ``utf-8`` encoding. +``pkg_resources.resource_listdir()`` +==================================== + +This function lists the entries in the package, both files and directories, +but it does not recurse into subdirectories, e.g.:: + + for entry in pkg_resources.listdir('my.package', 'subpackage'): + print(entry) + +This is easily rewritten using the following idiom:: + + for entry in importlib_resources.contents('my.package.subpackage'): + print(entry) + +Note: + +* ``pkg_resources`` does not require ``subpackage`` to be a Python package, + but ``importlib_resources`` does. +* ``importlib_resources.contents()`` returns an iterator, not a concrete + sequence. +* The order in which the elements are returned is undefined. +* ``importlib_resources.contents()`` returns *all* the entries in the + subpackage, i.e. both resources (files) and non-resources (directories). As + with ``pkg_resources.listdir()`` it does not recurse. + + +pkg_resources.resource_isdir() +============================== + +You can ask ``pkg_resources`` to tell you whether a particular resource inside +a package is a directory or not:: + + if pkg_resources.resource_isdir('my.package', 'resource'): + print('A directory') + +Because ``importlib_resources`` explicitly does not define directories as +resources, there's no direct equivalent. However, you can ask whether a +particular resource exists inside a package, and since directories are not +resources you can infer whether the resource is a directory or a file. Here +is a way to do that:: + + from importlib_resources import contents, is_resource + if 'resource' in contents('my.package') and \ + not is_resource('my.package', 'resource'): + print('It must be a directory') + +The reason you have to do it this way and not just call +``not is_resource('my.package', 'resource')`` is because this conditional will +also return False when ``resource`` is not an entry in ``my.package``. + + .. _`basic resource access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access diff --git a/importlib_resources/tests/data/subdirectory/__init__.py b/importlib_resources/tests/data/subdirectory/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/data/subdirectory/binary.file b/importlib_resources/tests/data/subdirectory/binary.file new file mode 100644 index 0000000000000000000000000000000000000000..eaf36c1daccfdf325514461cd1a2ffbc139b5464 GIT binary patch literal 4 LcmZQzWMT#Y01f~L literal 0 HcmV?d00001 diff --git a/importlib_resources/tests/data/ziptestdata.zip b/importlib_resources/tests/data/ziptestdata.zip deleted file mode 100644 index 49a89215d1f7e7c4ce56b1a595285c56752c3d6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 576 zcmWIWW@Zs#0D(o(A>LpHln@8fRhb1Psl_EJi6x2p@$s2?nI-Y@dIgmMa7|5}1>VAK zJG)tcia{7%Q&MJLVo{}DT4qiv10xeNLL+}>toP<_B^w=}CJ;u~SXz>%YiI`6`2U{= zLn=cK5at7wDlmjHgfO@<=rR~Gm;rg^K+z(gTnd9C0~Z6T1*d-;ng#M42#WzRvUflh zSb)vD9HK^V diff --git a/importlib_resources/tests/test_resource.py b/importlib_resources/tests/test_resource.py new file mode 100644 index 000000000000000..0baf2571a4d8bf2 --- /dev/null +++ b/importlib_resources/tests/test_resource.py @@ -0,0 +1,97 @@ +import sys +import unittest + +import importlib_resources as resources +from . import data +from . import util + + +class ResourceTests: + # Subclasses are expected to set the `data` attribute. + + def test_is_resource_good_path(self): + self.assertTrue(resources.is_resource(self.data, 'binary.file')) + + def test_is_resource_missing(self): + self.assertFalse(resources.is_resource(self.data, 'not-a-file')) + + def test_is_resource_subresource_directory(self): + # Directories are not resources. + self.assertFalse(resources.is_resource(self.data, 'subdirectory')) + + 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. + 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', + }) + + +class ResourceDiskTests(ResourceTests, unittest.TestCase): + def setUp(self): + self.data = data + + +class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): + pass + + +@unittest.skipIf(sys.version_info < (3,), 'No ResourceReader in Python 2') +class ResourceLoaderTests(unittest.TestCase): + def test_resource_contents(self): + package = util.create_package( + file=data, path=data.__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=data, path=data.__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=data, path=data.__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=data, path=data.__file__, + contents=['A', 'B', 'C', 'D/E', 'D/F']) + self.assertFalse(resources.is_resource(package, 'Z')) + + +class ResourceCornerCaseTests(unittest.TestCase): + def test_package_has_no_reader_fallback(self): + # Test odd ball packages which: + # 1. Do not have a ResourceReader as a loader + # 2. Are not on the file system + # 3. Are not in a zip file + module = util.create_package( + file=data, path=data.__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__ + self.assertFalse(resources.is_resource(module, 'A')) + + +if __name__ == '__main__': + unittest.main() diff --git a/importlib_resources/tests/util.py b/importlib_resources/tests/util.py index 8993baa7e05cef0..6fdb3787ab08c1c 100644 --- a/importlib_resources/tests/util.py +++ b/importlib_resources/tests/util.py @@ -7,6 +7,7 @@ from .. import abc as resources_abc from . import data +from . import zipdata from .._compat import ABC, Path, PurePath, FileNotFoundError @@ -16,7 +17,7 @@ ModuleSpec = None # type: ignore -def create_package(file, path, is_package=True): +def create_package(file, path, is_package=True, contents=()): class Reader(resources_abc.ResourceReader): def open_resource(self, path): self._path = path @@ -32,6 +33,23 @@ def resource_path(self, path_): else: return path + def is_resource(self, path_): + self._path = path_ + if isinstance(path, Exception): + raise path + for entry in contents: + parts = entry.split('/') + if len(parts) == 1 and parts[0] == path_: + return True + return False + + def contents(self): + if isinstance(path, Exception): + raise path + # There's no yield from in baseball, er, Python 2. + for entry in contents: + yield entry + name = 'testingpackage' # Unforunately importlib.util.module_from_spec() was not introduced until # Python 3.5. @@ -130,10 +148,9 @@ def test_useless_loader(self): class ZipSetup: - @classmethod def setUpClass(cls): - data_path = Path(data.__file__) + data_path = Path(zipdata.__file__) data_dir = data_path.parent cls._zip_path = str(data_dir / 'ziptestdata.zip') sys.path.append(cls._zip_path) diff --git a/importlib_resources/tests/zipdata/__init__.py b/importlib_resources/tests/zipdata/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/importlib_resources/tests/zipdata/ziptestdata.zip b/importlib_resources/tests/zipdata/ziptestdata.zip new file mode 100644 index 0000000000000000000000000000000000000000..9148e5bcc20248dcf18f786c143c82dd60291aa9 GIT binary patch literal 876 zcmWIWW@Zs#0D(o(A>LpHln@8fRhb1Psl_EJi6x2p@$s2?nI-Y@dIgmMa7_t)b>6~l zJG)tcia{7%Q&MJLVo{}DT4qiv10xeNLL+}>toP<_B^w=}CJ;u~SXz>%YiI`6`2U{= zLn=cK5at7wDlmjHgfO@<=rR~Gm;rg^K+z(gTnd9C0~Z6T1*d-;ng#M42#WzRvUflh zSb)vD9ewGXyCr z(G5Y5AcP_3u^0l0E@U&XMGnFYaYoc2!5viyuc3zzD6(K+Nn<2F6Hx+-l?|ke4G6ab K^~?ui1_l5n;KvaF literal 0 HcmV?d00001 diff --git a/update-tests.py b/update-tests.py index 2fec92767e8ca4e..ca0cb10aaaaf0a0 100755 --- a/update-tests.py +++ b/update-tests.py @@ -13,21 +13,19 @@ from zipfile import ZipFile RELPATH = 'importlib_resources/tests/data' -CONTENTS = [ - # filenames - the source will always be prepended by - # importlib_resources/tests/data/ziptestdata.zip and the destination will - # always be prepended by ziptestdata/ - '__init__.py', - 'binary.file', - 'utf-16.file', - 'utf-8.file', - ] - - -zip_file_path = os.path.join(RELPATH, 'ziptestdata.zip') - -with ZipFile(zip_file_path, 'w') as zf: - for filename in CONTENTS: - src = os.path.join(RELPATH, filename) - dst = os.path.join('ziptestdata', filename) - zf.write(src, dst) +BASEPATH = 'ziptestdata' +ZIP_FILE_PATH = 'importlib_resources/tests/zipdata/ziptestdata.zip' + + +with ZipFile(ZIP_FILE_PATH, 'w') as zf: + for dirpath, dirnames, filenames in os.walk(RELPATH): + for filename in filenames: + src = os.path.join(dirpath, filename) + if '__pycache__' in src: + continue + if src == ZIP_FILE_PATH: + continue + commonpath = os.path.commonpath((RELPATH, dirpath)) + dst = os.path.join(BASEPATH, dirpath[len(commonpath)+1:], filename) + print(src, '->', dst) + zf.write(src, dst)