mirror of
https://github.com/python/cpython.git
synced 2025-10-07 15:42:02 +00:00
bpo-25711: Move _ZipImportResourceReader from importlib to zipimport. (GH-9406)
This commit is contained in:
parent
2a9c3805dd
commit
9da3961f36
3 changed files with 1067 additions and 952 deletions
|
@ -257,87 +257,3 @@ def contents(package: Package) -> Iterable[str]:
|
||||||
else:
|
else:
|
||||||
package_directory = Path(package.__spec__.origin).parent
|
package_directory = Path(package.__spec__.origin).parent
|
||||||
return os.listdir(package_directory)
|
return os.listdir(package_directory)
|
||||||
|
|
||||||
|
|
||||||
# Private implementation of ResourceReader and get_resource_reader() called
|
|
||||||
# from zipimport.c. Don't use these directly! We're implementing these in
|
|
||||||
# Python because 1) it's easier, 2) zipimport may get rewritten in Python
|
|
||||||
# itself at some point, so doing this all in C would difficult and a waste of
|
|
||||||
# effort.
|
|
||||||
|
|
||||||
class _ZipImportResourceReader(resources_abc.ResourceReader):
|
|
||||||
"""Private class used to support ZipImport.get_resource_reader().
|
|
||||||
|
|
||||||
This class is allowed to reference all the innards and private parts of
|
|
||||||
the zipimporter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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}'
|
|
||||||
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".
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
# Called from zipimport.c
|
|
||||||
def _zipimport_get_resource_reader(zipimporter, fullname):
|
|
||||||
try:
|
|
||||||
if not zipimporter.is_package(fullname):
|
|
||||||
return None
|
|
||||||
except ZipImportError:
|
|
||||||
return None
|
|
||||||
return _ZipImportResourceReader(zipimporter, fullname)
|
|
||||||
|
|
|
@ -272,8 +272,16 @@ class zipimporter:
|
||||||
If 'fullname' is a package within the zip file, return the
|
If 'fullname' is a package within the zip file, return the
|
||||||
'ResourceReader' object for the package. Otherwise return None.
|
'ResourceReader' object for the package. Otherwise return None.
|
||||||
"""
|
"""
|
||||||
from importlib import resources
|
try:
|
||||||
return resources._zipimport_get_resource_reader(self, fullname)
|
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):
|
def __repr__(self):
|
||||||
|
@ -648,3 +656,74 @@ def _get_module_code(self, fullname):
|
||||||
return code, ispackage, modpath
|
return code, ispackage, modpath
|
||||||
else:
|
else:
|
||||||
raise ZipImportError(f"can't find module {fullname!r}", name=fullname)
|
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
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue