diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2018-09-19 06:28:06 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-19 06:28:06 (GMT) |
commit | 9da3961f364da2a3ced740230b85ffb4309238d3 (patch) | |
tree | 3bdba1d723f7e24e6c27139b398a4c2866974ff4 /Lib/zipimport.py | |
parent | 2a9c3805ddedf282881ef7811a561c70b74f80b1 (diff) | |
download | cpython-9da3961f364da2a3ced740230b85ffb4309238d3.zip cpython-9da3961f364da2a3ced740230b85ffb4309238d3.tar.gz cpython-9da3961f364da2a3ced740230b85ffb4309238d3.tar.bz2 |
bpo-25711: Move _ZipImportResourceReader from importlib to zipimport. (GH-9406)
Diffstat (limited to 'Lib/zipimport.py')
-rw-r--r-- | Lib/zipimport.py | 83 |
1 files changed, 81 insertions, 2 deletions
diff --git a/Lib/zipimport.py b/Lib/zipimport.py index 059f124..4017340 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -272,8 +272,16 @@ class zipimporter: If 'fullname' is a package within the zip file, return the 'ResourceReader' object for the package. Otherwise return None. """ - from importlib import resources - return resources._zipimport_get_resource_reader(self, fullname) + try: + if not self.is_package(fullname): + return None + except ZipImportError: + return None + if not _ZipImportResourceReader._registered: + from importlib.abc import ResourceReader + ResourceReader.register(_ZipImportResourceReader) + _ZipImportResourceReader._registered = True + return _ZipImportResourceReader(self, fullname) def __repr__(self): @@ -648,3 +656,74 @@ def _get_module_code(self, fullname): return code, ispackage, modpath else: raise ZipImportError(f"can't find module {fullname!r}", name=fullname) + + +class _ZipImportResourceReader: + """Private class used to support ZipImport.get_resource_reader(). + + This class is allowed to reference all the innards and private parts of + the zipimporter. + """ + _registered = False + + def __init__(self, zipimporter, fullname): + self.zipimporter = zipimporter + self.fullname = fullname + + def open_resource(self, resource): + fullname_as_path = self.fullname.replace('.', '/') + path = f'{fullname_as_path}/{resource}' + from io import BytesIO + try: + return BytesIO(self.zipimporter.get_data(path)) + except OSError: + raise FileNotFoundError(path) + + def resource_path(self, resource): + # All resources are in the zip file, so there is no path to the file. + # Raising FileNotFoundError tells the higher level API to extract the + # binary data and create a temporary file. + raise FileNotFoundError + + def is_resource(self, name): + # Maybe we could do better, but if we can get the data, it's a + # resource. Otherwise it isn't. + fullname_as_path = self.fullname.replace('.', '/') + path = f'{fullname_as_path}/{name}' + try: + self.zipimporter.get_data(path) + except OSError: + return False + return True + + def contents(self): + # This is a bit convoluted, because fullname will be a module path, + # but _files is a list of file names relative to the top of the + # archive's namespace. We want to compare file paths to find all the + # names of things inside the module represented by fullname. So we + # turn the module path of fullname into a file path relative to the + # top of the archive, and then we iterate through _files looking for + # names inside that "directory". + from pathlib import Path + fullname_path = Path(self.zipimporter.get_filename(self.fullname)) + relative_path = fullname_path.relative_to(self.zipimporter.archive) + # Don't forget that fullname names a package, so its path will include + # __init__.py, which we want to ignore. + assert relative_path.name == '__init__.py' + package_path = relative_path.parent + subdirs_seen = set() + for filename in self.zipimporter._files: + try: + relative = Path(filename).relative_to(package_path) + except ValueError: + continue + # If the path of the file (which is relative to the top of the zip + # namespace), relative to the package given when the resource + # reader was created, has a parent, then it's a name in a + # subdirectory and thus we skip it. + parent_name = relative.parent.name + if len(parent_name) == 0: + yield relative.name + elif parent_name not in subdirs_seen: + subdirs_seen.add(parent_name) + yield parent_name |