PK雈KEimportlib_resources/__init__.py"""Read resources contained within a package.""" import sys __version__ = '0.2' if sys.version_info >= (3,): from importlib_resources._py3 import ( Package, Resource, contents, is_resource, open_binary, open_text, path, read_binary, read_text) from importlib_resources.abc import ResourceReader else: from importlib_resources._py2 import ( contents, is_resource, open_binary, open_text, path, read_binary, read_text) PK;KwIEEimportlib_resources/_compat.pyfrom __future__ import absolute_import # flake8: noqa try: from pathlib import Path, PurePath except ImportError: from pathlib2 import Path, PurePath # type: ignore try: from abc import ABC # type: ignore except ImportError: from abc import ABCMeta class ABC(object): # type: ignore __metaclass__ = ABCMeta try: FileNotFoundError = FileNotFoundError # type: ignore except NameError: FileNotFoundError = OSError PKKj~s-s-importlib_resources/_py2.pyimport os import errno import tempfile from ._compat import FileNotFoundError from contextlib import contextmanager from importlib import import_module from io import BytesIO, TextIOWrapper, open as io_open from pathlib2 import Path from zipfile import ZipFile def _get_package(package): # `package` will be a string or a module. Always return a module which is # a package, otherwise raise an exception. if isinstance(package, basestring): # noqa: F821 module = import_module(package) else: module = package if not hasattr(module, '__path__'): raise TypeError("{!r} is not a package".format(package)) return module def _normalize_path(path): # Ensure that the incoming `path`, which may be a string or a Path object, # is a bare file name with no hierarchy. str_path = str(path) parent, file_name = os.path.split(str_path) if parent: raise ValueError("{!r} must be only a file name".format(path)) else: return file_name def open_binary(package, resource): """Return a file-like object opened for binary reading of the resource.""" resource = _normalize_path(resource) package = _get_package(package) # Using pathlib doesn't work well here due to the lack of 'strict' argument # for pathlib.Path.resolve() prior to Python 3.6. package_path = os.path.dirname(package.__file__) relative_path = os.path.join(package_path, resource) full_path = os.path.abspath(relative_path) try: return io_open(full_path, 'rb') except IOError: # This might be a package in a zip file. zipimport provides a loader # with a functioning get_data() method, however we have to strip the # archive (i.e. the .zip file's name) off the front of the path. This # is because the zipimport loader in Python 2 doesn't actually follow # PEP 302. It should allow the full path, but actually requires that # the path be relative to the zip file. try: loader = package.__loader__ full_path = relative_path[len(loader.archive)+1:] data = loader.get_data(full_path) except (IOError, AttributeError): package_name = package.__name__ message = '{!r} resource not found in {!r}'.format( resource, package_name) raise FileNotFoundError(message) else: return BytesIO(data) def open_text(package, resource, encoding='utf-8', errors='strict'): """Return a file-like object opened for text reading of the resource.""" resource = _normalize_path(resource) package = _get_package(package) # Using pathlib doesn't work well here due to the lack of 'strict' argument # for pathlib.Path.resolve() prior to Python 3.6. package_path = os.path.dirname(package.__file__) relative_path = os.path.join(package_path, resource) full_path = os.path.abspath(relative_path) try: return io_open(full_path, mode='r', encoding=encoding, errors=errors) except IOError: # This might be a package in a zip file. zipimport provides a loader # with a functioning get_data() method, however we have to strip the # archive (i.e. the .zip file's name) off the front of the path. This # is because the zipimport loader in Python 2 doesn't actually follow # PEP 302. It should allow the full path, but actually requires that # the path be relative to the zip file. try: loader = package.__loader__ full_path = relative_path[len(loader.archive)+1:] data = loader.get_data(full_path) except (IOError, AttributeError): package_name = package.__name__ message = '{!r} resource not found in {!r}'.format( resource, package_name) raise FileNotFoundError(message) else: return TextIOWrapper(BytesIO(data), encoding, errors) def read_binary(package, resource): """Return the binary contents of the resource.""" resource = _normalize_path(resource) package = _get_package(package) with open_binary(package, resource) as fp: return fp.read() def read_text(package, resource, encoding='utf-8', errors='strict'): """Return the decoded string of the resource. The decoding-related arguments have the same semantics as those of bytes.decode(). """ resource = _normalize_path(resource) package = _get_package(package) with open_text(package, resource, encoding, errors) as fp: return fp.read() @contextmanager def path(package, resource): """A context manager providing a file path object to the resource. If the resource does not already exist on its own on the file system, a temporary file will be created. If the file was created, the file will be deleted upon exiting the context manager (no exception is raised if the file was deleted prior to the context manager exiting). """ resource = _normalize_path(resource) package = _get_package(package) package_directory = Path(package.__file__).parent file_path = package_directory / resource # If the file actually exists on the file system, just return it. # Otherwise, it's probably in a zip file, so we need to create a temporary # file and copy the contents into that file, hence the contextmanager to # clean up the temp file resource. if file_path.exists(): yield file_path else: with open_binary(package, resource) as fp: data = fp.read() # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' # blocks due to the need to close the temporary file to work on Windows # properly. fd, raw_path = tempfile.mkstemp() try: os.write(fd, data) os.close(fd) yield Path(raw_path) finally: try: os.remove(raw_path) except FileNotFoundError: pass def is_resource(package, name): """True if name is a resource inside package. Directories are *not* resources. """ package = _get_package(package) _normalize_path(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 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 / 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 / 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! if path.parts[:len(relpath.parts)] != relpath.parts: continue 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 PKxKAq00importlib_resources/_py3.pyimport os import sys import tempfile from . import abc as resources_abc from builtins import open as builtins_open from contextlib import contextmanager, suppress from importlib import import_module from importlib.abc import ResourceLoader from io import BytesIO, TextIOWrapper from pathlib import Path from types import ModuleType from typing import Iterator, Optional, Set, Union # noqa: F401 from typing import cast from typing.io import BinaryIO, TextIO from zipfile import ZipFile Package = Union[ModuleType, str] if sys.version_info >= (3, 6): Resource = Union[str, os.PathLike] # pragma: ge35 else: Resource = str # pragma: le35 def _get_package(package) -> ModuleType: if hasattr(package, '__spec__'): if package.__spec__.submodule_search_locations is None: raise TypeError('{!r} is not a package'.format( package.__spec__.name)) else: return package else: module = import_module(package) if module.__spec__.submodule_search_locations is None: raise TypeError('{!r} is not a package'.format(package)) else: return module def _normalize_path(path) -> str: str_path = str(path) parent, file_name = os.path.split(str_path) if parent: raise ValueError('{!r} must be only a file name'.format(path)) else: 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_binary(package: Package, resource: Resource) -> BinaryIO: """Return a file-like object opened for binary reading of the resource.""" resource = _normalize_path(resource) package = _get_package(package) reader = _get_resource_reader(package) if reader is not None: return reader.open_resource(resource) # 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, resource) try: return builtins_open(full_path, mode='rb') 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) data = None if hasattr(package.__spec__.loader, 'get_data'): with suppress(IOError): data = loader.get_data(full_path) if data is None: package_name = package.__spec__.name message = '{!r} resource not found in {!r}'.format( resource, package_name) raise FileNotFoundError(message) else: return BytesIO(data) def open_text(package: Package, resource: Resource, encoding: str = 'utf-8', errors: str = 'strict') -> TextIO: """Return a file-like object opened for text reading of the resource.""" resource = _normalize_path(resource) package = _get_package(package) reader = _get_resource_reader(package) if reader is not None: return TextIOWrapper(reader.open_resource(resource), 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, resource) try: return builtins_open( full_path, mode='r', encoding=encoding, errors=errors) 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) data = None if hasattr(package.__spec__.loader, 'get_data'): with suppress(IOError): data = loader.get_data(full_path) if data is None: package_name = package.__spec__.name message = '{!r} resource not found in {!r}'.format( resource, package_name) raise FileNotFoundError(message) else: return TextIOWrapper(BytesIO(data), encoding, errors) def read_binary(package: Package, resource: Resource) -> bytes: """Return the binary contents of the resource.""" resource = _normalize_path(resource) package = _get_package(package) with open_binary(package, resource) as fp: return fp.read() def read_text(package: Package, resource: Resource, encoding: str = 'utf-8', errors: str = 'strict') -> str: """Return the decoded string of the resource. The decoding-related arguments have the same semantics as those of bytes.decode(). """ resource = _normalize_path(resource) package = _get_package(package) with open_text(package, resource, encoding, errors) as fp: return fp.read() @contextmanager def path(package: Package, resource: Resource) -> Iterator[Path]: """A context manager providing a file path object to the resource. If the resource does not already exist on its own on the file system, a temporary file will be created. If the file was created, the file will be deleted upon exiting the context manager (no exception is raised if the file was deleted prior to the context manager exiting). """ resource = _normalize_path(resource) package = _get_package(package) reader = _get_resource_reader(package) if reader is not None: try: yield Path(reader.resource_path(resource)) return except FileNotFoundError: pass # Fall-through for both the lack of resource_path() *and* if # resource_path() raises FileNotFoundError. package_directory = Path(package.__spec__.origin).parent file_path = package_directory / resource if file_path.exists(): yield file_path else: with open_binary(package, resource) as fp: data = fp.read() # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' # blocks due to the need to close the temporary file to work on # Windows properly. fd, raw_path = tempfile.mkstemp() try: os.write(fd, data) os.close(fd) yield Path(raw_path) finally: try: os.remove(raw_path) except FileNotFoundError: pass def is_resource(package: Package, name: str) -> bool: """True if `name` is a resource inside `package`. Directories are *not* resources. """ package = _get_package(package) _normalize_path(name) reader = _get_resource_reader(package) if reader is not None: return reader.is_resource(name) try: package_contents = set(contents(package)) except (NotADirectoryError, FileNotFoundError): return False if 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 / 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 / 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 # Is the package a namespace package? By definition, namespace packages # cannot have resources. if (package.__spec__.origin == 'namespace' and not package.__spec__.has_location): 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! if path.parts[:len(relpath.parts)] != relpath.parts: continue 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 PKꞅKm8 importlib_resources/abc.pyfrom __future__ import absolute_import from ._compat import ABC, FileNotFoundError from abc import abstractmethod # We use mypy's comment syntax here since this file must be compatible with # both Python 2 and 3. try: from typing import BinaryIO, Iterator, Text # noqa: F401 except ImportError: # Python 2 pass class ResourceReader(ABC): """Abstract base class for loaders to provide resource reading support.""" @abstractmethod def open_resource(self, resource): # type: (Text) -> BinaryIO """Return an opened, file-like object for binary reading. The 'resource' argument is expected to represent only a file name. If the resource cannot be found, FileNotFoundError is raised. """ # This deliberately raises FileNotFoundError instead of # NotImplementedError so that if this method is accidentally called, # it'll still do the right thing. raise FileNotFoundError @abstractmethod def resource_path(self, resource): # type: (Text) -> Text """Return the file system path to the specified resource. The 'resource' argument is expected to represent only a file name. If the resource does not exist on the file system, raise FileNotFoundError. """ # This deliberately raises FileNotFoundError instead of # 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 package.""" raise FileNotFoundError PKxKg5OO importlib_resources/docs/abc.rst.. _abc: ======================== The ResourceReader ABC ======================== ``importlib_resources`` relies heavily on a package's module loader_ for accessing that package's resources, with fallbacks for common cases when the loader doesn't provide this information (e.g. for zip files in versions of Python before 3.7). These fallbacks are not perfect, and there will be cases where custom loaders are implemented which subvert the usefulness of these fallback. For this reason, a new `abstract base class`_ is introduced for loaders that want to participate in resource introspection and access. Loaders can implement the ``ResourceReader`` ABC to provide support for resources where the fallbacks don't work, or for providing more efficient access to resources. ``importlib_resources`` will first [#fn1]_ introspect the package's loader to see if it supports the ``ResourceReader`` interface. If it does, it will use that for all resource access. The ``ResourceReader`` ABC decorates its methods with ``@abstractmethod`` to indicate that they must all be overridden by the loader that implements this interface. However, the default implementation of each of these methods is to raise :py:exc:`FileNotFoundError` rather than :py:exc:`NotImplementedError`. This is so that if the ABC method is accidentally called, ``importlib_resources`` should still be able to try its fallbacks. .. py:class:: ResourceReader The abstract base class for loaders to implement if they provide resource reading and access support. Loaders should implement all of these methods. .. py:method:: open_resource(resource) Open the named *resource* for binary reading. The argument must be filename-like, i.e. it cannot have any path separators in the string. If the resource cannot be found, :py:exc:`FileNotFoundError` should be raised. :param resource: The resource within the package to open. :type resource: importlib_resources.Resource :return: A stream open for binary reading. Text decoding is handled at a higher level. :rtype: typing.BinaryIO :raises FileNotFoundError: when the named resource is not found within the package. .. py:method:: resource_path(resource) Return the path to the named *resource* as found on the file system. If the resource is not natively accessible on the file system (e.g. can't be accessed through :py:class:`pathlib.Path`), then :py:exc:`FileNotFoundError` should be raised. In this case, :py:meth:`importlib_resources.path()` will read the contents of the resource, create a temporary file, and return a context manager that will manage the lifetime of the temporary file. :param resource: The resource within the package to open. :type resource: importlib_resources.Resource :return: The path to the named resource, relative to the package. :rtype: str :raises FileNotFoundError: when the named resource is not found within the package, or the resources is not directly accessible on the file system. .. py:method:: is_resource(name) Return a boolean indicating whether *name* is a resource within the package. *Remember that directories are not resources!* :param name: A filename-like string (i.e. no path separators) to check whether it is a resource within the package. :type resource: str :return: Flag indicating whether *name* is a resource or not. :rtype: bool :raises FileNotFoundError: when the named resource is not found within the package. .. py:method:: contents() Return a sequence of all the contents of the package. This is like doing a directory listing. This returns resources (e.g. file names) and non-resource (e.g. subdirectories) alike. Thus, entries in this sequence may or may not be resources. :return: A sequence of string names. :rtype: Iterator[str] .. rubric:: Footnotes .. [#fn1] In Python 3 only. ``importlib_resources`` does not support the ``ResourceReader`` ABC for Python 2. .. _loader: https://docs.python.org/3/reference/import.html#finders-and-loaders .. _`abstract base class`: https://docs.python.org/3/library/abc.html PKxK>Ց9 importlib_resources/docs/api.rst.. _api: ========================= importlib_resources API ========================= ``importlib_resources`` exposes a small number of functions, and it references a limited number of types, both as arguments to functions and as return types. Types ===== .. py:class:: Package ``Package`` types are defined as ``Union[ModuleType, str]``. This means that where the function describes accepting a ``Package``, you can pass in either a module or a string. Note that in Python 2, the module object *must* have a ``__path__`` attribute, while in Python 3, the module object must have a resolvable ``__spec__.submodule_search_locations`` that is not ``None``. .. py:class:: Resource This type describes the resource names passed into the various functions in this package. For Python 3.6 and later, this is defined as ``Union[str, os.PathLike]``. For earlier versions (which don't have ``os.PathLike``), this is defined as ``str``. Functions ========= .. py:function:: importlib_resources.open_binary(package, resource) Open for binary reading the *resource* within *package*. :param package: A package name or module object. See above for the API that such module objects must support. :type package: ``Package`` :param resource: The name of the resource to open within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type resource: ``Resource`` :returns: a binary I/O stream open for reading. :rtype: ``typing.io.BinaryIO`` .. py:function:: importlib_resources.open_text(package, resource, encoding='utf-8', errors='strict') Open for text reading the *resource* within *package*. By default, the resource is opened for reading as UTF-8. :param package: A package name or module object. See above for the API that such module objects must support. :type package: ``Package`` :param resource: The name of the resource to open within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type resource: ``Resource`` :param encoding: The encoding to open the resource in. *encoding* has the same meaning as with :py:func:`open`. :type encoding: str :param errors: This parameter has the same meaning as with :py:func:`open`. :type errors: str :returns: an I/O stream open for reading. :rtype: ``typing.TextIO`` .. py:function:: importlib_resources.read_binary(package, resource) Read and return the contents of the *resource* within *package* as ``bytes``. :param package: A package name or module object. See above for the API that such module objects must support. :type package: ``Package`` :param resource: The name of the resource to read within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type resource: ``Resource`` :returns: the contents of the resource. :rtype: ``bytes`` .. py:function:: importlib_resources.read_text(package, resource, encoding='utf-8', errors='strict') Read and return the contents of *resource* within *package* as a ``str`` [#fn1]_. By default, the contents are read as strict UTF-8. :param package: A package name or module object. See above for the API that such module objects must support. :type package: ``Package`` :param resource: The name of the resource to read within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type resource: ``Resource`` :param encoding: The encoding to read the contents of the resource in. *encoding* has the same meaning as with :py:func:`open`. :type encoding: str :param errors: This parameter has the same meaning as with :py:func:`open`. :type errors: str :returns: the contents of the resource. :rtype: ``str`` .. py:function:: importlib_resources.path(package, resource) Return the path to the *resource* as an actual file system path. This function returns a `context manager`_ for use in a ``with``-statement. The context manager provides a :py:class:`pathlib.Path` object. Exiting the context manager cleans up any temporary file created when the resource needs to be extracted from e.g. a zip file. :param package: A package name or module object. See above for the API that such module objects must support. :type package: ``Package`` :param resource: The name of the resource to read within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type resource: ``Resource`` :returns: A context manager for use in a ``with``-statement. Entering 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, name) Return ``True`` if there is a resource named *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 name: The name of the resource to read within *package*. *resource* may not contain path separators and it may not have sub-resources (i.e. it cannot be a directory). :type name: ``str`` :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 ``str`` .. rubric:: Footnotes .. [#fn1] The contents are returned as a ``str`` in Python 3, but as a ``unicode`` in Python 2. .. _`context manager`: https://docs.python.org/3/library/stdtypes.html#typecontextmanager PKiKS&importlib_resources/docs/changelog.rst========================== importlib_resources NEWS ========================== 0.2 (2017-12-13) ================ * **Backward incompatible change**. Split the ``open()`` and ``read()`` calls into separate binary and text versions, i.e. ``open_binary()``, ``open_text()``, ``read_binary()``, and ``read_text()``. Closes #41 * Fix a bug where unrelated resources could be returned from ``contents()``. Closes #44 * Correctly prevent namespace packages from containing resources. Closes #20 0.1 (2017-12-05) ================ * Initial release. .. Local Variables: mode: change-log-mode indent-tabs-mode: nil sentence-end-double-space: t fill-column: 78 coding: utf-8 End: PKUKB importlib_resources/docs/conf.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # flake8: noqa # # importlib_resources documentation build configuration file, created by # sphinx-quickstart on Thu Nov 30 10:21:00 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'importlib_resources' copyright = '2017, Brett Cannon, Barry Warsaw' author = 'Brett Cannon, Barry Warsaw' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.1' # The full version, including alpha/beta/rc tags. release = '0.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'importlib_resourcesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'importlib_resources.tex', 'importlib\\_resources Documentation', 'Brett Cannon, Barry Warsaw', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'importlib_resources', 'importlib_resources Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'importlib_resources', 'importlib_resources Documentation', author, 'importlib_resources', 'One line description of project.', 'Miscellaneous'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } PKKȓR"importlib_resources/docs/index.rst================================ Welcome to importlib_resources ================================ ``importlib_resources`` is a library which provides for access to *resources* in Python packages. It provides functionality similar to ``pkg_resources`` `Basic Resource Access`_ API, but without all of the overhead and performance problems of ``pkg_resources``. In our terminology, a *resource* is a file that is located within an importable `Python package`_. Resources can live on the file system, in a zip file, or in any place that has a loader_ supporting the appropriate API for reading resources. Directories are not resources. ``importlib_resources`` is a standalone version of the API available for users of Python 2.7, or Python 3.4 through 3.6. It is available in Python 3.7's standard library as ``importlib.resources``. Its API is currently `provisional`_. This documentation includes a general :ref:`usage ` guide and a :ref:`migration ` guide for projects that want to adopt ``importlib_resources`` instead of ``pkg_resources``. .. toctree:: :maxdepth: 2 :caption: Contents: using.rst migration.rst api.rst abc.rst changelog.rst Project details =============== * Project home: https://gitlab.com/python-devs/importlib_resources * Report bugs at: https://gitlab.com/python-devs/importlib_resources/issues * Code hosting: https://gitlab.com/python-devs/importlib_resources.git * Documentation: http://importlib_resources.readthedocs.io/ Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _`Basic Resource Access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access .. _`provisional`: https://www.python.org/dev/peps/pep-0411/ .. _`Python package`: https://docs.python.org/3/reference/import.html#packages .. _loader: https://docs.python.org/3/reference/import.html#finders-and-loaders PK雈KWL&importlib_resources/docs/migration.rst.. _migration: ================= Migration guide ================= The following guide will help you migrate common ``pkg_resources`` APIs to ``importlib_resources``. Only a small number of the most common APIs are supported by ``importlib_resources``, so projects that use other features (e.g. entry points) will have to find other solutions. ``importlib_resources`` primarily supports the following `basic resource 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; since only files are allowed as resources, file names in the ``importlib_resources`` API may *not* include path separators (e.g. slashes). pkg_resources.resource_filename() ================================= ``resource_filename()`` is one of the more interesting APIs because it guarantees that the return value names a file on the file system. This means that if the resource is in a zip file, ``pkg_resources()`` will extract the file and return the name of the temporary file it created. The problem is that ``pkg_resources()`` also *implicitly* cleans up this temporary file, without control over its lifetime by the programmer. ``importlib_resources`` takes a different approach. Its equivalent API is the ``path()`` function, which returns a context manager providing a :py:class:`pathlib.Path` object. This means users have both the flexibility and responsibility to manage the lifetime of the temporary file. Note though that if the resource is *already* on the file system, ``importlib_resources`` still returns a context manager, but nothing needs to get cleaned up. Here's an example from ``pkg_resources()``:: path = pkg_resources.resource_filename('my.package', 'resource.dat') The best way to convert this is with the following idiom:: with importlib_resources.path('my.package', 'resource.dat') as path: # Do something with path. After the with-statement exits, any # temporary file created will be immediately cleaned up. That's all fine if you only need the file temporarily, but what if you need it to stick around for a while? One way of doing this is to use an :py:class:`contextlib.ExitStack` instance and manage the resource explicitly:: from contextlib import ExitStack file_manager = ExitStack() path = file_manager.enter_context( importlib_resources.path('my.package', 'resource.dat')) Now ``path`` will continue to exist until you explicitly call ``file_manager.close()``. What if you want the file to exist until the process exits, or you can't pass ``file_manager`` around in your code? Use an :py:mod:`atexit` handler:: import atexit file_manager = ExitStack() atexit.register(file_manager.close) path = file_manager.enter_context( importlib_resources.path('my.package', 'resource.dat')) Assuming your Python interpreter exits gracefully, the temporary file will be cleaned up when Python exits. pkg_resources.resource_stream() =============================== ``pkg_resources.resource_stream()`` returns a readable file-like object opened in binary mode. When you read from the returned file-like object, you get bytes. E.g.:: with pkg_resources.resource_stream('my.package', 'resource.dat') as fp: my_bytes = fp.read() The equivalent code in ``importlib_resources`` is pretty straightforward:: with importlib_resources.open_binary('my.package', 'resource.dat') as fp: my_bytes = fp.read() pkg_resources.resource_string() =============================== In Python 2, ``pkg_resources.resource_string()`` returns the contents of a resource as a ``str``. In Python 3, this function is a misnomer; it actually returns the contents of the named resource as ``bytes``. That's why the following example is often written for clarity as:: from pkg_resources import resource_string as resource_bytes contents = resource_bytes('my.package', 'resource.dat') This can be easily rewritten like so:: contents = importlib_resources.read_binary('my.package', 'resource.dat') 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 PKxK//"importlib_resources/docs/using.rst.. _using: =========================== Using importlib_resources =========================== ``importlib_resources`` is a library that leverages Python's import system to provide access to *resources* within *packages*. Given that this library is built on top of the import system, it is highly efficient and easy to use. This library's philosophy is that, if you can import a package, you can access resources within that package. Resources can be opened or read, in either binary or text mode. What exactly do we mean by "a resource"? It's easiest to think about the metaphor of files and directories on the file system, though it's important to keep in mind that this is just a metaphor. Resources and packages **do not** have to exist as physical files and directories on the file system. If you have a file system layout such as:: data/ __init__.py one/ __init__.py resource1.txt two/ __init__.py resource2.txt then the directories are ``data``, ``data/one``, and ``data/two``. Each of these are also Python packages by virtue of the fact that they all contain ``__init__.py`` files [#fn1]_. That means that in Python, all of these import statements work:: import data import data.one from data import two Each import statement gives you a Python *module* corresponding to the ``__init__.py`` file in each of the respective directories. These modules are packages since packages are just special module instances that have an additional attribute, namely a ``__path__`` [#fn2]_. In this analogy then, resources are just files within a package directory, so ``data/one/resource1.txt`` and ``data/two/resource2.txt`` are both resources, as are the ``__init__.py`` files in all the directories. However the package directories themselves are *not* resources; anything that contains other things (i.e. directories) are not themselves resources. Resources are always accessed relative to the package that they live in. You cannot access a resource within a subdirectory inside a package. This means that ``resource1.txt`` is a resource within the ``data.one`` package, but neither ``resource2.txt`` nor ``two/resource2.txt`` are resources within the ``data`` package. If a directory isn't a package, it can't be imported and thus can't contain resources. Even when this hierarchical structure isn't represented by physical files and directories, the model still holds. So zip files can contain packages and resources, as could databases or other storage medium. In fact, while ``importlib_resources`` supports physical file systems and zip files by default, anything that can be loaded with a Python import system `loader`_ can provide resources, as long as the loader implements the :ref:`ResourceReader ` abstract base class. Example ======= Let's say you are writing an email parsing library and in your test suite you have a sample email message in a file called ``message.eml``. You would like to access the contents of this file for your tests, so you put this in your project under the ``email/tests/data/message.eml`` path. Let's say your unit tests live in ``email/tests/test_email.py``. Your test could read the data file by doing something like:: data_dir = os.path.join(os.path.dirname(__file__), 'tests', 'data') data_path = os.path.join(data_dir, 'message.eml') with open(data_path, encoding='utf-8') as fp: eml = fp.read() But there's a problem with this! The use of ``__file__`` doesn't work if your package lives inside a zip file, since in that case this code does not live on the file system. You could use the `pkg_resources API`_ like so:: # In Python 3, resource_string() actually returns bytes! from pkg_resources import resource_string as resource_bytes eml = resource_bytes('email.tests.data', 'message.eml').decode('utf-8') This requires you to make Python packages of both ``email/tests`` and ``email/tests/data``, by placing an empty ``__init__.py`` files in each of those directories. **This is a requirement for importlib_resources too!** The problem with the ``pkg_resources`` approach is that, depending on the structure of your package, ``pkg_resources`` can be very inefficient even to just import. ``pkg_resources`` is a sort of grab-bag of APIs and functionalities, and to support all of this, it sometimes has to do a ton of work at import time, e.g. to scan every package on your ``sys.path``. This can have a serious negative impact on things like command line startup time for Python implement commands. ``importlib_resources`` solves this by being built entirely on the back of the stdlib :py:mod:`importlib`. By taking advantage of all the efficiencies in Python's import system, and the fact that it's built into Python, using ``importlib_resources`` can be much more performant. The equivalent code using ``importlib_resources`` would look like:: from importlib_resources import read_text # Reads contents with UTF-8 encoding and returns str. eml = read_text('email.tests.data', 'message.eml') Packages or package names ========================= All of the ``importlib_resources`` APIs take a *package* as their first parameter, but this can either be a package name (as a ``str``) or an actual module object, though the module *must* be a package [#fn3]_. If a string is passed in, it must name an importable Python package, and this is first imported. Thus the above example could also be written as:: import email.tests.data eml = read_text(email.tests.data, 'message.eml') File system or zip file ======================= In general you never have to worry whether your package is on the file system or in a zip file, as the ``importlib_resources`` APIs hide those details from you. Sometimes though, you need a path to an actual file on the file system. For example, some SSL APIs require a certificate file to be specified by a real file system path, and C's ``dlopen()`` function also requires a real file system path. To support this, ``importlib_resources`` provides an API that will extract the resource from a zip file to a temporary file, and return the file system path to this temporary file as a :py:class:`pathlib.Path` object. In order to properly clean up this temporary file, what's actually returned is a context manager that you can use in a ``with``-statement:: from importlib_resources import path with path(email.tests.data, 'message.eml') as eml: third_party_api_requiring_file_system_path(eml) You can use all the standard :py:mod:`contextlib` APIs to manage this context manager. .. rubric:: Footnotes .. [#fn1] We're ignoring `PEP 420 `_ style namespace packages, since ``importlib_resources`` does not support resources within namespace packages. Also, the example assumes that the parent directory containing ``data/`` is on ``sys.path``. .. [#fn2] As of `PEP 451 `_ this information is also available on the module's ``__spec__.submodule_search_locations`` attribute, which will not be ``None`` for packages. .. [#fn3] Specifically, this means that in Python 2, the module object must have an ``__path__`` attribute, while in Python 3, the module's ``__spec__.submodule_search_locations`` must not be ``None``. Otherwise a ``TypeError`` is raised. .. _`pkg_resources API`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access .. _`loader`: https://docs.python.org/3/reference/import.html#finders-and-loaders PK;K*importlib_resources/docs/_static/.ignoremePK;K%importlib_resources/tests/__init__.pyPKK=&importlib_resources/tests/test_open.pyimport unittest import importlib_resources as resources from . import data01 from . import util from .._compat import FileNotFoundError class CommonBinaryTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): with resources.open_binary(package, path): pass class CommonTextTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): with resources.open_text(package, path): pass class OpenTests: def test_open_binary(self): with resources.open_binary(self.data, 'utf-8.file') as fp: result = fp.read() self.assertEqual(result, b'Hello, UTF-8 world!\n') def test_open_text_default_encoding(self): with resources.open_text(self.data, 'utf-8.file') as fp: result = fp.read() 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: 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: self.assertRaises(UnicodeError, fp.read) 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') def test_open_binary_FileNotFoundError(self): self.assertRaises( 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') class OpenDiskTests(OpenTests, unittest.TestCase): def setUp(self): self.data = data01 class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): pass if __name__ == '__main__': unittest.main() PKK+&importlib_resources/tests/test_path.pyimport unittest import importlib_resources as resources from . import data01 from . import util class CommonTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): with resources.path(package, path): pass class PathTests: def test_reading(self): # Path should be readable. # Test also implicitly verifies the returned object is a pathlib.Path # instance. with resources.path(self.data, 'utf-8.file') as 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) class PathDiskTests(PathTests, unittest.TestCase): data = data01 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 # file system is removed inside the `with` stanza. with resources.path(self.data, 'utf-8.file') as path: path.unlink() if __name__ == '__main__': unittest.main() PKKOC;;&importlib_resources/tests/test_read.pyimport unittest import importlib_resources as resources from . import data01 from . import util class CommonBinaryTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): resources.read_binary(package, path) class CommonTextTests(util.CommonTests, unittest.TestCase): def execute(self, package, path): resources.read_text(package, path) class ReadTests: def test_read_binary(self): result = resources.read_binary(self.data, 'binary.file') self.assertEqual(result, b'\0\1\2\3') def test_read_text_default_encoding(self): result = resources.read_text(self.data, 'utf-8.file') 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') 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') 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') class ReadDiskTests(ReadTests, unittest.TestCase): data = data01 class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): pass if __name__ == '__main__': unittest.main() PKxK 1*importlib_resources/tests/test_resource.pyimport sys import unittest import importlib_resources as resources from . import data01 from . import zipdata02 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 = data01 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=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']) 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']) 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']) 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=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__ 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'}) @unittest.skipIf(sys.version_info < (3,), 'No namespace packages in Python 2') class NamespaceTest(unittest.TestCase): def test_namespaces_cant_have_resources(self): contents = set(resources.contents( 'importlib_resources.tests.data03.namespace')) self.assertEqual(len(contents), 0) # 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( 'importlib_resources.tests.data03.namespace', 'resource1.txt')) # We should get an exception if we try to read it or open it. self.assertRaises( FileNotFoundError, resources.open_text, 'importlib_resources.tests.data03.namespace', 'resource1.txt') self.assertRaises( FileNotFoundError, resources.open_binary, 'importlib_resources.tests.data03.namespace', 'resource1.txt') self.assertRaises( FileNotFoundError, resources.read_text, 'importlib_resources.tests.data03.namespace', 'resource1.txt') self.assertRaises( FileNotFoundError, resources.read_binary, 'importlib_resources.tests.data03.namespace', 'resource1.txt') if __name__ == '__main__': unittest.main() PKKm%88!importlib_resources/tests/util.pyimport abc import importlib import io import sys import types import unittest from .. import abc as resources_abc from . import data01 from . import zipdata01 from .._compat import ABC, Path, PurePath, FileNotFoundError try: from importlib.machinery import ModuleSpec except ImportError: ModuleSpec = None # type: ignore def create_package(file, path, is_package=True, contents=()): class Reader(resources_abc.ResourceReader): def open_resource(self, path): self._path = path if isinstance(file, Exception): raise file else: return file def resource_path(self, path_): self._path = path_ if isinstance(path, Exception): raise 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. module = types.ModuleType(name) if ModuleSpec is None: # Python 2. module.__name__ = name module.__file__ = 'does-not-exist' if is_package: module.__path__ = [] else: # Python 3. loader = Reader() spec = ModuleSpec( name, loader, origin='does-not-exist', is_package=is_package) module.__spec__ = spec module.__loader__ = loader return module class CommonTests(ABC): @abc.abstractmethod def execute(self, package, path): raise NotImplementedError def test_package_name(self): # Passing in the package name should succeed. self.execute(data01.__name__, 'utf-8.file') def test_package_object(self): # Passing in the package itself should succeed. self.execute(data01, 'utf-8.file') def test_string_path(self): # Passing in a string for the path should succeed. path = 'utf-8.file' self.execute(data01, path) @unittest.skipIf(sys.version_info < (3, 6), 'requires os.PathLike support') def test_pathlib_path(self): # Passing in a pathlib.PurePath object for the path should succeed. path = PurePath('utf-8.file') self.execute(data01, path) def test_absolute_path(self): # An absolute path is a ValueError. path = Path(__file__) full_path = path.parent/'utf-8.file' with self.assertRaises(ValueError): self.execute(data01, full_path) def test_relative_path(self): # A reative path is a ValueError. with self.assertRaises(ValueError): self.execute(data01, '../data01/utf-8.file') def test_importing_module_as_side_effect(self): # The anchor package can already be imported. del sys.modules[data01.__name__] self.execute(data01.__name__, 'utf-8.file') def test_non_package_by_name(self): # The anchor package cannot be a module. with self.assertRaises(TypeError): self.execute(__name__, 'utf-8.file') def test_non_package_by_package(self): # The anchor package cannot be a module. with self.assertRaises(TypeError): module = sys.modules['importlib_resources.tests.util'] self.execute(module, 'utf-8.file') @unittest.skipIf(sys.version_info < (3,), 'No ResourceReader in Python 2') def test_resource_opener(self): bytes_data = io.BytesIO(b'Hello, world!') package = create_package(file=bytes_data, path=FileNotFoundError()) self.execute(package, 'utf-8.file') self.assertEqual(package.__loader__._path, 'utf-8.file') @unittest.skipIf(sys.version_info < (3,), 'No ResourceReader in Python 2') def test_resource_path(self): bytes_data = io.BytesIO(b'Hello, world!') path = __file__ package = create_package(file=bytes_data, path=path) self.execute(package, 'utf-8.file') self.assertEqual(package.__loader__._path, 'utf-8.file') def test_useless_loader(self): package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) with self.assertRaises(FileNotFoundError): self.execute(package, 'utf-8.file') class ZipSetupBase: ZIP_MODULE = None @classmethod def setUpClass(cls): data_path = 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 class ZipSetup(ZipSetupBase): ZIP_MODULE = zipdata01 # type: ignore PKK,importlib_resources/tests/data01/__init__.pyPKK,importlib_resources/tests/data01/binary.filePKK"<,,,importlib_resources/tests/data01/utf-16.fileHello, UTF-16 world! PKKš+importlib_resources/tests/data01/utf-8.fileHello, UTF-8 world! PKK9importlib_resources/tests/data01/subdirectory/__init__.pyPKK9importlib_resources/tests/data01/subdirectory/binary.filePKK,importlib_resources/tests/data02/__init__.pyPKK0importlib_resources/tests/data02/one/__init__.pyPKK 2importlib_resources/tests/data02/one/resource1.txtone resource PKK0importlib_resources/tests/data02/two/__init__.pyPKK]o, 2importlib_resources/tests/data02/two/resource2.txttwo resource PKxK,importlib_resources/tests/data03/__init__.pyPKxK8importlib_resources/tests/data03/namespace/resource1.txtPKxK?importlib_resources/tests/data03/namespace/portion1/__init__.pyPKxK?importlib_resources/tests/data03/namespace/portion2/__init__.pyPKK/importlib_resources/tests/zipdata01/__init__.pyPKKxll3importlib_resources/tests/zipdata01/ziptestdata.zipPKvKziptestdata/__init__.pyPKvKziptestdata/binary.filePKvK"<,,ziptestdata/utf-16.fileHello, UTF-16 world! PKvKšziptestdata/utf-8.fileHello, UTF-8 world! PKvK$ziptestdata/subdirectory/__init__.pyPKvK$ziptestdata/subdirectory/binary.filePKvKziptestdata/__init__.pyPKvK5ziptestdata/binary.filePKvK"<,,nziptestdata/utf-16.filePKvKšziptestdata/utf-8.filePKvK$ziptestdata/subdirectory/__init__.pyPKvK$Yziptestdata/subdirectory/binary.filePKPKK/importlib_resources/tests/zipdata02/__init__.pyPKK'|3importlib_resources/tests/zipdata02/ziptestdata.zipPKvKziptestdata/__init__.pyPKvKziptestdata/one/__init__.pyPKvK ziptestdata/one/resource1.txtone resource PKvKziptestdata/two/__init__.pyPKvK]o, ziptestdata/two/resource2.txttwo resource PKvKziptestdata/__init__.pyPKvK5ziptestdata/one/__init__.pyPKvK nziptestdata/one/resource1.txtPKvKziptestdata/two/__init__.pyPKvK]o, ziptestdata/two/resource2.txtPKm7PK;K 33)importlib_resources-0.2.dist-info/LICENSECopyright 2017 Brett Cannon, Barry Warsaw Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. PK!Hf$Wd'importlib_resources-0.2.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,Q0343 /, (-JLR()*M ILR(4KM̫#DPK!H%R*importlib_resources-0.2.dist-info/METADATATMs8 WTto3jYosL4C,qC*98uP8$ؠ`Jxn9T뇘Ļ:1f{1mJ$l`:A[' 0`}-Yoo Pf0ރMP:jbřyj 6ߝznt1ow| U~{]1撾Nc] oN˧ONt>霛ʑ958ed(KC ?tx= Bc(Ԕ;vWWF|:0VS^(eU\-&3x>2&GQ/Rl -Ўڎ2UvO֩c)i_VGѲOő1' ,{=ؽ_OcUIT5DĪ!v04YҞ29(9Tfv14 t0/z@cT*;'&Z;tVrۿl6F9IVzTi 3d9ܴ<\$ 1̣v7]ꆣ't/U`[hh5-fB@79Lpc [ } (zja fHJ$aU6XY4#5d w.YkxlVZ֚g?ۈ㒦Vcˀ;^fy:{lXqez+e{5PK!HXʧ(importlib_resources-0.2.dist-info/RECORDɖH@- ^0(* n8mvuVuZtֆtx"@Mu8_ j cXO ڽS6?@'rOǎ"= jlyOy"t]1[,#!޾hg#80thNk'`W*zgxL;xe(g1gLqv I`<z{> R\g-egϢX ̍Dp 8>!#^+tPq o/z&coh:ІޭJ^C4.}nCc˦7NW&RttaYٙ`YF$?z) B7>.u~ʙ&#ia{F^ ).V~:\w/*s;fȽ`'qvחd' b*4s'/n4nJ_f{{+NDjDׯJgoޮ^򌫃r5jWpT|1W5 ,,-ߒ4YՑ9 |importlib_resources/docs/api.rstPKiKS&importlib_resources/docs/changelog.rstPKUKB importlib_resources/docs/conf.pyPKKȓR"timportlib_resources/docs/index.rstPK雈KWL&Bimportlib_resources/docs/migration.rstPKxK//"?importlib_resources/docs/using.rstPK;K*importlib_resources/docs/_static/.ignoremePK;K%importlib_resources/tests/__init__.pyPKK=&9importlib_resources/tests/test_open.pyPKK+&mimportlib_resources/tests/test_path.pyPKKOC;;&Kimportlib_resources/tests/test_read.pyPKxK 1*importlib_resources/tests/test_resource.pyPKKm%88!importlib_resources/tests/util.pyPKK,)1importlib_resources/tests/data01/__init__.pyPKK,s1importlib_resources/tests/data01/binary.filePKK"<,,,1importlib_resources/tests/data01/utf-16.filePKKš+72importlib_resources/tests/data01/utf-8.filePKK92importlib_resources/tests/data01/subdirectory/__init__.pyPKK92importlib_resources/tests/data01/subdirectory/binary.filePKK,F3importlib_resources/tests/data02/__init__.pyPKK03importlib_resources/tests/data02/one/__init__.pyPKK 23importlib_resources/tests/data02/one/resource1.txtPKK0;4importlib_resources/tests/data02/two/__init__.pyPKK]o, 24importlib_resources/tests/data02/two/resource2.txtPKxK,4importlib_resources/tests/data03/__init__.pyPKxK805importlib_resources/tests/data03/namespace/resource1.txtPKxK?5importlib_resources/tests/data03/namespace/portion1/__init__.pyPKxK?5importlib_resources/tests/data03/namespace/portion2/__init__.pyPKK/@6importlib_resources/tests/zipdata01/__init__.pyPKKxll36importlib_resources/tests/zipdata01/ziptestdata.zipPKK/J:importlib_resources/tests/zipdata02/__init__.pyPKK'|3:importlib_resources/tests/zipdata02/ziptestdata.zipPK;K 33)=importlib_resources-0.2.dist-info/LICENSEPK!Hf$Wd'@importlib_resources-0.2.dist-info/WHEELPK!H%R*@importlib_resources-0.2.dist-info/METADATAPK!HXʧ(Cimportlib_resources-0.2.dist-info/RECORDPK**nI