You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
13 KiB
303 lines
13 KiB
5 years ago
|
From a6a3a716c97b438ed64d5368e3462c6787ba0b85 Mon Sep 17 00:00:00 2001
|
||
|
From: Ken Dreyer <kdreyer@redhat.com>
|
||
|
Date: Fri, 8 Nov 2019 09:13:38 -0700
|
||
|
Subject: [PATCH] raise NotImplementedError on Python 2
|
||
|
|
||
|
---
|
||
|
importlib_resources/__init__.py | 4 +-
|
||
|
importlib_resources/_py2.py | 270 ----------------------------------------
|
||
|
2 files changed, 1 insertion(+), 273 deletions(-)
|
||
|
delete mode 100644 importlib_resources/_py2.py
|
||
|
|
||
|
diff --git a/importlib_resources/__init__.py b/importlib_resources/__init__.py
|
||
|
index fab437a..b012e5c 100644
|
||
|
--- a/importlib_resources/__init__.py
|
||
|
+++ b/importlib_resources/__init__.py
|
||
|
@@ -28,9 +28,7 @@ elif sys.version_info >= (3,):
|
||
|
from importlib_resources.abc import ResourceReader
|
||
|
__all__.extend(['Package', 'Resource', 'ResourceReader'])
|
||
|
else:
|
||
|
- from importlib_resources._py2 import (
|
||
|
- contents, is_resource, open_binary, open_text, path, read_binary,
|
||
|
- read_text)
|
||
|
+ raise NotImplementedError('Fedora package does not support Python 2')
|
||
|
|
||
|
|
||
|
__version__ = read_text('importlib_resources', 'version.txt').strip()
|
||
|
diff --git a/importlib_resources/_py2.py b/importlib_resources/_py2.py
|
||
|
deleted file mode 100644
|
||
|
index 376f0e3..0000000
|
||
|
--- a/importlib_resources/_py2.py
|
||
|
+++ /dev/null
|
||
|
@@ -1,270 +0,0 @@
|
||
|
-import 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):
|
||
|
- """Normalize a path by ensuring it is a string.
|
||
|
-
|
||
|
- If the resulting string contains path separators, an exception is raised.
|
||
|
- """
|
||
|
- 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):
|
||
|
- """Normalize a path by ensuring it is a string.
|
||
|
-
|
||
|
- If the resulting string contains path separators, an exception is raised.
|
||
|
- """
|
||
|
- 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: nocover
|
||
|
- 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 an iterable 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:
|
||
|
- return os.listdir(str(package_directory))
|
||
|
- 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: nocover
|
||
|
- # 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
|
||
|
- subdirs_returned = []
|
||
|
- 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:
|
||
|
- subdirs_returned.append(subparts[0])
|
||
|
- elif len(subparts) > 1: # pragma: nobranch
|
||
|
- subdir = subparts[0]
|
||
|
- if subdir not in subdirs_seen:
|
||
|
- subdirs_seen.add(subdir)
|
||
|
- subdirs_returned.append(subdir)
|
||
|
- return subdirs_returned
|