mirror of https://github.com/python/cpython
bpo-46118: Move importlib.resources to its own package. (#30176)
* bpo-46118: Move importlib.resources to its own package. * Expand compatibility shims with documentation and explicit imports.
This commit is contained in:
parent
2cf7d02b99
commit
e712a5b277
|
@ -14,8 +14,19 @@ except ImportError:
|
||||||
from ._abc import Loader
|
from ._abc import Loader
|
||||||
import abc
|
import abc
|
||||||
import warnings
|
import warnings
|
||||||
from typing import BinaryIO, Iterable, Text
|
|
||||||
from typing import Protocol, runtime_checkable
|
# for compatibility with Python 3.10
|
||||||
|
from .resources.abc import ResourceReader, Traversable, TraversableResources
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Loader', 'Finder', 'MetaPathFinder', 'PathEntryFinder',
|
||||||
|
'ResourceLoader', 'InspectLoader', 'ExecutionLoader',
|
||||||
|
'FileLoader', 'SourceLoader',
|
||||||
|
|
||||||
|
# for compatibility with Python 3.10
|
||||||
|
'ResourceReader', 'Traversable', 'TraversableResources',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _register(abstract_cls, *classes):
|
def _register(abstract_cls, *classes):
|
||||||
|
@ -307,136 +318,3 @@ class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_register(SourceLoader, machinery.SourceFileLoader)
|
_register(SourceLoader, machinery.SourceFileLoader)
|
||||||
|
|
||||||
|
|
||||||
class ResourceReader(metaclass=abc.ABCMeta):
|
|
||||||
"""Abstract base class for loaders to provide resource reading support."""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def open_resource(self, resource: 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
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def resource_path(self, resource: 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
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_resource(self, path: Text) -> bool:
|
|
||||||
"""Return True if the named 'path' is a resource.
|
|
||||||
|
|
||||||
Files are resources, directories are not.
|
|
||||||
"""
|
|
||||||
raise FileNotFoundError
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def contents(self) -> Iterable[str]:
|
|
||||||
"""Return an iterable of entries in `package`."""
|
|
||||||
raise FileNotFoundError
|
|
||||||
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class Traversable(Protocol):
|
|
||||||
"""
|
|
||||||
An object with a subset of pathlib.Path methods suitable for
|
|
||||||
traversing directories and opening files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def iterdir(self):
|
|
||||||
"""
|
|
||||||
Yield Traversable objects in self
|
|
||||||
"""
|
|
||||||
|
|
||||||
def read_bytes(self):
|
|
||||||
"""
|
|
||||||
Read contents of self as bytes
|
|
||||||
"""
|
|
||||||
with self.open('rb') as strm:
|
|
||||||
return strm.read()
|
|
||||||
|
|
||||||
def read_text(self, encoding=None):
|
|
||||||
"""
|
|
||||||
Read contents of self as text
|
|
||||||
"""
|
|
||||||
with self.open(encoding=encoding) as strm:
|
|
||||||
return strm.read()
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_dir(self) -> bool:
|
|
||||||
"""
|
|
||||||
Return True if self is a directory
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def is_file(self) -> bool:
|
|
||||||
"""
|
|
||||||
Return True if self is a file
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def joinpath(self, child):
|
|
||||||
"""
|
|
||||||
Return Traversable child in self
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __truediv__(self, child):
|
|
||||||
"""
|
|
||||||
Return Traversable child in self
|
|
||||||
"""
|
|
||||||
return self.joinpath(child)
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def open(self, mode='r', *args, **kwargs):
|
|
||||||
"""
|
|
||||||
mode may be 'r' or 'rb' to open as text or binary. Return a handle
|
|
||||||
suitable for reading (same as pathlib.Path.open).
|
|
||||||
|
|
||||||
When opening as text, accepts encoding parameters such as those
|
|
||||||
accepted by io.TextIOWrapper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def name(self) -> str:
|
|
||||||
"""
|
|
||||||
The base name of this object without any parent references.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class TraversableResources(ResourceReader):
|
|
||||||
"""
|
|
||||||
The required interface for providing traversable
|
|
||||||
resources.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def files(self):
|
|
||||||
"""Return a Traversable object for the loaded package."""
|
|
||||||
|
|
||||||
def open_resource(self, resource):
|
|
||||||
return self.files().joinpath(resource).open('rb')
|
|
||||||
|
|
||||||
def resource_path(self, resource):
|
|
||||||
raise FileNotFoundError(resource)
|
|
||||||
|
|
||||||
def is_resource(self, path):
|
|
||||||
return self.files().joinpath(path).is_file()
|
|
||||||
|
|
||||||
def contents(self):
|
|
||||||
return (item.name for item in self.files().iterdir())
|
|
||||||
|
|
|
@ -1,122 +1,12 @@
|
||||||
import collections
|
"""
|
||||||
import operator
|
Compatibility shim for .resources.readers as found on Python 3.10.
|
||||||
import pathlib
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
from . import abc
|
Consumers that can rely on Python 3.11 should use the other
|
||||||
|
module directly.
|
||||||
|
"""
|
||||||
|
|
||||||
from ._itertools import unique_everseen
|
from .resources.readers import (
|
||||||
|
FileReader, ZipReader, MultiplexedPath, NamespaceReader,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ['FileReader', 'ZipReader', 'MultiplexedPath', 'NamespaceReader']
|
||||||
def remove_duplicates(items):
|
|
||||||
return iter(collections.OrderedDict.fromkeys(items))
|
|
||||||
|
|
||||||
|
|
||||||
class FileReader(abc.TraversableResources):
|
|
||||||
def __init__(self, loader):
|
|
||||||
self.path = pathlib.Path(loader.path).parent
|
|
||||||
|
|
||||||
def resource_path(self, resource):
|
|
||||||
"""
|
|
||||||
Return the file system path to prevent
|
|
||||||
`resources.path()` from creating a temporary
|
|
||||||
copy.
|
|
||||||
"""
|
|
||||||
return str(self.path.joinpath(resource))
|
|
||||||
|
|
||||||
def files(self):
|
|
||||||
return self.path
|
|
||||||
|
|
||||||
|
|
||||||
class ZipReader(abc.TraversableResources):
|
|
||||||
def __init__(self, loader, module):
|
|
||||||
_, _, name = module.rpartition('.')
|
|
||||||
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
|
|
||||||
self.archive = loader.archive
|
|
||||||
|
|
||||||
def open_resource(self, resource):
|
|
||||||
try:
|
|
||||||
return super().open_resource(resource)
|
|
||||||
except KeyError as exc:
|
|
||||||
raise FileNotFoundError(exc.args[0])
|
|
||||||
|
|
||||||
def is_resource(self, path):
|
|
||||||
# workaround for `zipfile.Path.is_file` returning true
|
|
||||||
# for non-existent paths.
|
|
||||||
target = self.files().joinpath(path)
|
|
||||||
return target.is_file() and target.exists()
|
|
||||||
|
|
||||||
def files(self):
|
|
||||||
return zipfile.Path(self.archive, self.prefix)
|
|
||||||
|
|
||||||
|
|
||||||
class MultiplexedPath(abc.Traversable):
|
|
||||||
"""
|
|
||||||
Given a series of Traversable objects, implement a merged
|
|
||||||
version of the interface across all objects. Useful for
|
|
||||||
namespace packages which may be multihomed at a single
|
|
||||||
name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *paths):
|
|
||||||
self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
|
|
||||||
if not self._paths:
|
|
||||||
message = 'MultiplexedPath must contain at least one path'
|
|
||||||
raise FileNotFoundError(message)
|
|
||||||
if not all(path.is_dir() for path in self._paths):
|
|
||||||
raise NotADirectoryError('MultiplexedPath only supports directories')
|
|
||||||
|
|
||||||
def iterdir(self):
|
|
||||||
files = (file for path in self._paths for file in path.iterdir())
|
|
||||||
return unique_everseen(files, key=operator.attrgetter('name'))
|
|
||||||
|
|
||||||
def read_bytes(self):
|
|
||||||
raise FileNotFoundError(f'{self} is not a file')
|
|
||||||
|
|
||||||
def read_text(self, *args, **kwargs):
|
|
||||||
raise FileNotFoundError(f'{self} is not a file')
|
|
||||||
|
|
||||||
def is_dir(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_file(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def joinpath(self, child):
|
|
||||||
# first try to find child in current paths
|
|
||||||
for file in self.iterdir():
|
|
||||||
if file.name == child:
|
|
||||||
return file
|
|
||||||
# if it does not exist, construct it with the first path
|
|
||||||
return self._paths[0] / child
|
|
||||||
|
|
||||||
__truediv__ = joinpath
|
|
||||||
|
|
||||||
def open(self, *args, **kwargs):
|
|
||||||
raise FileNotFoundError(f'{self} is not a file')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._paths[0].name
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
paths = ', '.join(f"'{path}'" for path in self._paths)
|
|
||||||
return f'MultiplexedPath({paths})'
|
|
||||||
|
|
||||||
|
|
||||||
class NamespaceReader(abc.TraversableResources):
|
|
||||||
def __init__(self, namespace_path):
|
|
||||||
if 'NamespacePath' not in str(namespace_path):
|
|
||||||
raise ValueError('Invalid path')
|
|
||||||
self.path = MultiplexedPath(*list(namespace_path))
|
|
||||||
|
|
||||||
def resource_path(self, resource):
|
|
||||||
"""
|
|
||||||
Return the file system path to prevent
|
|
||||||
`resources.path()` from creating a temporary
|
|
||||||
copy.
|
|
||||||
"""
|
|
||||||
return str(self.path.joinpath(resource))
|
|
||||||
|
|
||||||
def files(self):
|
|
||||||
return self.path
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from ._legacy import (
|
||||||
Resource,
|
Resource,
|
||||||
)
|
)
|
||||||
|
|
||||||
from importlib.abc import ResourceReader
|
from .abc import ResourceReader
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
|
@ -0,0 +1,136 @@
|
||||||
|
import abc
|
||||||
|
from typing import BinaryIO, Iterable, Text
|
||||||
|
from typing import runtime_checkable, Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceReader(metaclass=abc.ABCMeta):
|
||||||
|
"""Abstract base class for loaders to provide resource reading support."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def open_resource(self, resource: 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
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def resource_path(self, resource: 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
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_resource(self, path: Text) -> bool:
|
||||||
|
"""Return True if the named 'path' is a resource.
|
||||||
|
|
||||||
|
Files are resources, directories are not.
|
||||||
|
"""
|
||||||
|
raise FileNotFoundError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def contents(self) -> Iterable[str]:
|
||||||
|
"""Return an iterable of entries in `package`."""
|
||||||
|
raise FileNotFoundError
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class Traversable(Protocol):
|
||||||
|
"""
|
||||||
|
An object with a subset of pathlib.Path methods suitable for
|
||||||
|
traversing directories and opening files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def iterdir(self):
|
||||||
|
"""
|
||||||
|
Yield Traversable objects in self
|
||||||
|
"""
|
||||||
|
|
||||||
|
def read_bytes(self):
|
||||||
|
"""
|
||||||
|
Read contents of self as bytes
|
||||||
|
"""
|
||||||
|
with self.open('rb') as strm:
|
||||||
|
return strm.read()
|
||||||
|
|
||||||
|
def read_text(self, encoding=None):
|
||||||
|
"""
|
||||||
|
Read contents of self as text
|
||||||
|
"""
|
||||||
|
with self.open(encoding=encoding) as strm:
|
||||||
|
return strm.read()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_dir(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return True if self is a directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_file(self) -> bool:
|
||||||
|
"""
|
||||||
|
Return True if self is a file
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def joinpath(self, child):
|
||||||
|
"""
|
||||||
|
Return Traversable child in self
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __truediv__(self, child):
|
||||||
|
"""
|
||||||
|
Return Traversable child in self
|
||||||
|
"""
|
||||||
|
return self.joinpath(child)
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def open(self, mode='r', *args, **kwargs):
|
||||||
|
"""
|
||||||
|
mode may be 'r' or 'rb' to open as text or binary. Return a handle
|
||||||
|
suitable for reading (same as pathlib.Path.open).
|
||||||
|
|
||||||
|
When opening as text, accepts encoding parameters such as those
|
||||||
|
accepted by io.TextIOWrapper.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def name(self) -> str:
|
||||||
|
"""
|
||||||
|
The base name of this object without any parent references.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TraversableResources(ResourceReader):
|
||||||
|
"""
|
||||||
|
The required interface for providing traversable
|
||||||
|
resources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def files(self):
|
||||||
|
"""Return a Traversable object for the loaded package."""
|
||||||
|
|
||||||
|
def open_resource(self, resource):
|
||||||
|
return self.files().joinpath(resource).open('rb')
|
||||||
|
|
||||||
|
def resource_path(self, resource):
|
||||||
|
raise FileNotFoundError(resource)
|
||||||
|
|
||||||
|
def is_resource(self, path):
|
||||||
|
return self.files().joinpath(path).is_file()
|
||||||
|
|
||||||
|
def contents(self):
|
||||||
|
return (item.name for item in self.files().iterdir())
|
|
@ -0,0 +1,122 @@
|
||||||
|
import collections
|
||||||
|
import operator
|
||||||
|
import pathlib
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from . import abc
|
||||||
|
|
||||||
|
from ._itertools import unique_everseen
|
||||||
|
|
||||||
|
|
||||||
|
def remove_duplicates(items):
|
||||||
|
return iter(collections.OrderedDict.fromkeys(items))
|
||||||
|
|
||||||
|
|
||||||
|
class FileReader(abc.TraversableResources):
|
||||||
|
def __init__(self, loader):
|
||||||
|
self.path = pathlib.Path(loader.path).parent
|
||||||
|
|
||||||
|
def resource_path(self, resource):
|
||||||
|
"""
|
||||||
|
Return the file system path to prevent
|
||||||
|
`resources.path()` from creating a temporary
|
||||||
|
copy.
|
||||||
|
"""
|
||||||
|
return str(self.path.joinpath(resource))
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
class ZipReader(abc.TraversableResources):
|
||||||
|
def __init__(self, loader, module):
|
||||||
|
_, _, name = module.rpartition('.')
|
||||||
|
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
|
||||||
|
self.archive = loader.archive
|
||||||
|
|
||||||
|
def open_resource(self, resource):
|
||||||
|
try:
|
||||||
|
return super().open_resource(resource)
|
||||||
|
except KeyError as exc:
|
||||||
|
raise FileNotFoundError(exc.args[0])
|
||||||
|
|
||||||
|
def is_resource(self, path):
|
||||||
|
# workaround for `zipfile.Path.is_file` returning true
|
||||||
|
# for non-existent paths.
|
||||||
|
target = self.files().joinpath(path)
|
||||||
|
return target.is_file() and target.exists()
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return zipfile.Path(self.archive, self.prefix)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplexedPath(abc.Traversable):
|
||||||
|
"""
|
||||||
|
Given a series of Traversable objects, implement a merged
|
||||||
|
version of the interface across all objects. Useful for
|
||||||
|
namespace packages which may be multihomed at a single
|
||||||
|
name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *paths):
|
||||||
|
self._paths = list(map(pathlib.Path, remove_duplicates(paths)))
|
||||||
|
if not self._paths:
|
||||||
|
message = 'MultiplexedPath must contain at least one path'
|
||||||
|
raise FileNotFoundError(message)
|
||||||
|
if not all(path.is_dir() for path in self._paths):
|
||||||
|
raise NotADirectoryError('MultiplexedPath only supports directories')
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
files = (file for path in self._paths for file in path.iterdir())
|
||||||
|
return unique_everseen(files, key=operator.attrgetter('name'))
|
||||||
|
|
||||||
|
def read_bytes(self):
|
||||||
|
raise FileNotFoundError(f'{self} is not a file')
|
||||||
|
|
||||||
|
def read_text(self, *args, **kwargs):
|
||||||
|
raise FileNotFoundError(f'{self} is not a file')
|
||||||
|
|
||||||
|
def is_dir(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def joinpath(self, child):
|
||||||
|
# first try to find child in current paths
|
||||||
|
for file in self.iterdir():
|
||||||
|
if file.name == child:
|
||||||
|
return file
|
||||||
|
# if it does not exist, construct it with the first path
|
||||||
|
return self._paths[0] / child
|
||||||
|
|
||||||
|
__truediv__ = joinpath
|
||||||
|
|
||||||
|
def open(self, *args, **kwargs):
|
||||||
|
raise FileNotFoundError(f'{self} is not a file')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._paths[0].name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
paths = ', '.join(f"'{path}'" for path in self._paths)
|
||||||
|
return f'MultiplexedPath({paths})'
|
||||||
|
|
||||||
|
|
||||||
|
class NamespaceReader(abc.TraversableResources):
|
||||||
|
def __init__(self, namespace_path):
|
||||||
|
if 'NamespacePath' not in str(namespace_path):
|
||||||
|
raise ValueError('Invalid path')
|
||||||
|
self.path = MultiplexedPath(*list(namespace_path))
|
||||||
|
|
||||||
|
def resource_path(self, resource):
|
||||||
|
"""
|
||||||
|
Return the file system path to prevent
|
||||||
|
`resources.path()` from creating a temporary
|
||||||
|
copy.
|
||||||
|
"""
|
||||||
|
return str(self.path.joinpath(resource))
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return self.path
|
|
@ -0,0 +1,116 @@
|
||||||
|
"""
|
||||||
|
Interface adapters for low-level readers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import io
|
||||||
|
import itertools
|
||||||
|
from typing import BinaryIO, List
|
||||||
|
|
||||||
|
from .abc import Traversable, TraversableResources
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleReader(abc.ABC):
|
||||||
|
"""
|
||||||
|
The minimum, low-level interface required from a resource
|
||||||
|
provider.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def package(self):
|
||||||
|
# type: () -> str
|
||||||
|
"""
|
||||||
|
The name of the package for which this reader loads resources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def children(self):
|
||||||
|
# type: () -> List['SimpleReader']
|
||||||
|
"""
|
||||||
|
Obtain an iterable of SimpleReader for available
|
||||||
|
child containers (e.g. directories).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def resources(self):
|
||||||
|
# type: () -> List[str]
|
||||||
|
"""
|
||||||
|
Obtain available named resources for this virtual package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def open_binary(self, resource):
|
||||||
|
# type: (str) -> BinaryIO
|
||||||
|
"""
|
||||||
|
Obtain a File-like for a named resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.package.split('.')[-1]
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceHandle(Traversable):
|
||||||
|
"""
|
||||||
|
Handle to a named resource in a ResourceReader.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent, name):
|
||||||
|
# type: (ResourceContainer, str) -> None
|
||||||
|
self.parent = parent
|
||||||
|
self.name = name # type: ignore
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_dir(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def open(self, mode='r', *args, **kwargs):
|
||||||
|
stream = self.parent.reader.open_binary(self.name)
|
||||||
|
if 'b' not in mode:
|
||||||
|
stream = io.TextIOWrapper(*args, **kwargs)
|
||||||
|
return stream
|
||||||
|
|
||||||
|
def joinpath(self, name):
|
||||||
|
raise RuntimeError("Cannot traverse into a resource")
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceContainer(Traversable):
|
||||||
|
"""
|
||||||
|
Traversable container for a package's resources via its reader.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, reader):
|
||||||
|
# type: (SimpleReader) -> None
|
||||||
|
self.reader = reader
|
||||||
|
|
||||||
|
def is_dir(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
files = (ResourceHandle(self, name) for name in self.reader.resources)
|
||||||
|
dirs = map(ResourceContainer, self.reader.children())
|
||||||
|
return itertools.chain(files, dirs)
|
||||||
|
|
||||||
|
def open(self, *args, **kwargs):
|
||||||
|
raise IsADirectoryError()
|
||||||
|
|
||||||
|
def joinpath(self, name):
|
||||||
|
return next(
|
||||||
|
traversable for traversable in self.iterdir() if traversable.name == name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TraversableReader(TraversableResources, SimpleReader):
|
||||||
|
"""
|
||||||
|
A TraversableResources based on SimpleReader. Resource providers
|
||||||
|
may derive from this class to provide the TraversableResources
|
||||||
|
interface by supplying the SimpleReader interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return ResourceContainer(self)
|
|
@ -1,116 +1,14 @@
|
||||||
"""
|
"""
|
||||||
Interface adapters for low-level readers.
|
Compatibility shim for .resources.simple as found on Python 3.10.
|
||||||
|
|
||||||
|
Consumers that can rely on Python 3.11 should use the other
|
||||||
|
module directly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
from .resources.simple import (
|
||||||
import io
|
SimpleReader, ResourceHandle, ResourceContainer, TraversableReader,
|
||||||
import itertools
|
)
|
||||||
from typing import BinaryIO, List
|
|
||||||
|
|
||||||
from .abc import Traversable, TraversableResources
|
__all__ = [
|
||||||
|
'SimpleReader', 'ResourceHandle', 'ResourceContainer', 'TraversableReader',
|
||||||
|
]
|
||||||
class SimpleReader(abc.ABC):
|
|
||||||
"""
|
|
||||||
The minimum, low-level interface required from a resource
|
|
||||||
provider.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def package(self):
|
|
||||||
# type: () -> str
|
|
||||||
"""
|
|
||||||
The name of the package for which this reader loads resources.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def children(self):
|
|
||||||
# type: () -> List['SimpleReader']
|
|
||||||
"""
|
|
||||||
Obtain an iterable of SimpleReader for available
|
|
||||||
child containers (e.g. directories).
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def resources(self):
|
|
||||||
# type: () -> List[str]
|
|
||||||
"""
|
|
||||||
Obtain available named resources for this virtual package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def open_binary(self, resource):
|
|
||||||
# type: (str) -> BinaryIO
|
|
||||||
"""
|
|
||||||
Obtain a File-like for a named resource.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.package.split('.')[-1]
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceHandle(Traversable):
|
|
||||||
"""
|
|
||||||
Handle to a named resource in a ResourceReader.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, parent, name):
|
|
||||||
# type: (ResourceContainer, str) -> None
|
|
||||||
self.parent = parent
|
|
||||||
self.name = name # type: ignore
|
|
||||||
|
|
||||||
def is_file(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_dir(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def open(self, mode='r', *args, **kwargs):
|
|
||||||
stream = self.parent.reader.open_binary(self.name)
|
|
||||||
if 'b' not in mode:
|
|
||||||
stream = io.TextIOWrapper(*args, **kwargs)
|
|
||||||
return stream
|
|
||||||
|
|
||||||
def joinpath(self, name):
|
|
||||||
raise RuntimeError("Cannot traverse into a resource")
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceContainer(Traversable):
|
|
||||||
"""
|
|
||||||
Traversable container for a package's resources via its reader.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, reader):
|
|
||||||
# type: (SimpleReader) -> None
|
|
||||||
self.reader = reader
|
|
||||||
|
|
||||||
def is_dir(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def is_file(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def iterdir(self):
|
|
||||||
files = (ResourceHandle(self, name) for name in self.reader.resources)
|
|
||||||
dirs = map(ResourceContainer, self.reader.children())
|
|
||||||
return itertools.chain(files, dirs)
|
|
||||||
|
|
||||||
def open(self, *args, **kwargs):
|
|
||||||
raise IsADirectoryError()
|
|
||||||
|
|
||||||
def joinpath(self, name):
|
|
||||||
return next(
|
|
||||||
traversable for traversable in self.iterdir() if traversable.name == name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TraversableReader(TraversableResources, SimpleReader):
|
|
||||||
"""
|
|
||||||
A TraversableResources based on SimpleReader. Resource providers
|
|
||||||
may derive from this class to provide the TraversableResources
|
|
||||||
interface by supplying the SimpleReader interface.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def files(self):
|
|
||||||
return ResourceContainer(self)
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import unittest
|
||||||
|
|
||||||
from importlib import resources
|
from importlib import resources
|
||||||
|
|
||||||
from importlib._adapters import (
|
from importlib.resources._adapters import (
|
||||||
CompatibilityFiles,
|
CompatibilityFiles,
|
||||||
wrap_spec,
|
wrap_spec,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Moved importlib.resources and its related functionality to a package.
|
Loading…
Reference in New Issue