mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-25 14:25:05 +00:00
Merge branch 'feature/UpdatePackageRes' into dev
This commit is contained in:
commit
80879bc91d
9 changed files with 296 additions and 96 deletions
|
@ -1,7 +1,7 @@
|
|||
### 3.28.0 (2023-xx-xx xx:xx:00 UTC)
|
||||
|
||||
* Update html5lib 1.1 (f87487a) to 1.2-dev (3e500bb)
|
||||
* Update package resource API 63.2.0 (3ae44cd) to 67.3.2 (b9bf2ec)
|
||||
* Update package resource API 63.2.0 (3ae44cd) to 67.5.1 (f51eccd)
|
||||
* Update urllib3 1.26.13 (25fbd5f) to 1.26.14 (a06c05c)
|
||||
* Change remove calls to legacy py2 fix encoding function
|
||||
* Change requirements for pure py3
|
||||
|
@ -12,6 +12,7 @@
|
|||
[develop changelog]
|
||||
|
||||
* Add logging around the restart/shutdown event
|
||||
* Update package resource API 63.2.0 (3ae44cd) to 67.3.2 (b9bf2ec)
|
||||
|
||||
|
||||
### 3.27.11 (2023-03-06 23:40:00 UTC)
|
||||
|
|
|
@ -12,6 +12,12 @@ The package resource API is designed to work with normal filesystem packages,
|
|||
.egg files, and unpacked .egg files. It can also work in a limited way with
|
||||
.zip files and with custom PEP 302 loaders that support the ``get_data()``
|
||||
method.
|
||||
|
||||
This module is deprecated. Users are directed to
|
||||
`importlib.resources <https://docs.python.org/3/library/importlib.resources.html>`_
|
||||
and
|
||||
`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
|
||||
instead.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
@ -112,6 +118,12 @@ _namespace_handlers = None
|
|||
_namespace_packages = None
|
||||
|
||||
|
||||
warnings.warn("pkg_resources is deprecated as an API", DeprecationWarning)
|
||||
|
||||
|
||||
_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)
|
||||
|
||||
|
||||
class PEP440Warning(RuntimeWarning):
|
||||
"""
|
||||
Used when there is an issue with a version or specifier not complying with
|
||||
|
@ -914,9 +926,7 @@ class WorkingSet:
|
|||
list(map(shadow_set.add, self))
|
||||
|
||||
for project_name in plugin_projects:
|
||||
|
||||
for dist in plugin_env[project_name]:
|
||||
|
||||
req = [dist.as_requirement()]
|
||||
|
||||
try:
|
||||
|
@ -1389,6 +1399,38 @@ def safe_version(version):
|
|||
return re.sub('[^A-Za-z0-9.]+', '-', version)
|
||||
|
||||
|
||||
def _forgiving_version(version):
|
||||
"""Fallback when ``safe_version`` is not safe enough
|
||||
>>> parse_version(_forgiving_version('0.23ubuntu1'))
|
||||
<Version('0.23.dev0+sanitized.ubuntu1')>
|
||||
>>> parse_version(_forgiving_version('0.23-'))
|
||||
<Version('0.23.dev0+sanitized')>
|
||||
>>> parse_version(_forgiving_version('0.-_'))
|
||||
<Version('0.dev0+sanitized')>
|
||||
>>> parse_version(_forgiving_version('42.+?1'))
|
||||
<Version('42.dev0+sanitized.1')>
|
||||
>>> parse_version(_forgiving_version('hello world'))
|
||||
<Version('0.dev0+sanitized.hello.world')>
|
||||
"""
|
||||
version = version.replace(' ', '.')
|
||||
match = _PEP440_FALLBACK.search(version)
|
||||
if match:
|
||||
safe = match["safe"]
|
||||
rest = version[len(safe):]
|
||||
else:
|
||||
safe = "0"
|
||||
rest = version
|
||||
local = f"sanitized.{_safe_segment(rest)}".strip(".")
|
||||
return f"{safe}.dev0+{local}"
|
||||
|
||||
|
||||
def _safe_segment(segment):
|
||||
"""Convert an arbitrary string into a safe segment"""
|
||||
segment = re.sub('[^A-Za-z0-9.]+', '-', segment)
|
||||
segment = re.sub('-[^A-Za-z0-9]+', '-', segment)
|
||||
return re.sub(r'\.[^A-Za-z0-9]+', '.', segment).strip(".-")
|
||||
|
||||
|
||||
def safe_extra(extra):
|
||||
"""Convert an arbitrary string to a standard 'extra' name
|
||||
|
||||
|
@ -1822,7 +1864,6 @@ class ZipProvider(EggProvider):
|
|||
|
||||
# FIXME: 'ZipProvider._extract_resource' is too complex (12)
|
||||
def _extract_resource(self, manager, zip_path): # noqa: C901
|
||||
|
||||
if zip_path in self._index():
|
||||
for name in self._index()[zip_path]:
|
||||
last = self._extract_resource(manager, os.path.join(zip_path, name))
|
||||
|
@ -1836,7 +1877,6 @@ class ZipProvider(EggProvider):
|
|||
'"os.rename" and "os.unlink" are not supported ' 'on this platform'
|
||||
)
|
||||
try:
|
||||
|
||||
real_path = manager.get_cache_path(self.egg_name, self._parts(zip_path))
|
||||
|
||||
if self._is_current(real_path, zip_path):
|
||||
|
@ -2637,7 +2677,7 @@ class Distribution:
|
|||
@property
|
||||
def hashcmp(self):
|
||||
return (
|
||||
self.parsed_version,
|
||||
self._forgiving_parsed_version,
|
||||
self.precedence,
|
||||
self.key,
|
||||
self.location,
|
||||
|
@ -2695,6 +2735,32 @@ class Distribution:
|
|||
|
||||
return self._parsed_version
|
||||
|
||||
@property
|
||||
def _forgiving_parsed_version(self):
|
||||
try:
|
||||
return self.parsed_version
|
||||
except packaging.version.InvalidVersion as ex:
|
||||
self._parsed_version = parse_version(_forgiving_version(self.version))
|
||||
|
||||
notes = "\n".join(getattr(ex, "__notes__", [])) # PEP 678
|
||||
msg = f"""!!\n\n
|
||||
*************************************************************************
|
||||
{str(ex)}\n{notes}
|
||||
|
||||
This is a long overdue deprecation.
|
||||
For the time being, `pkg_resources` will use `{self._parsed_version}`
|
||||
as a replacement to avoid breaking existing environments,
|
||||
but no future compatibility is guaranteed.
|
||||
|
||||
If you maintain package {self.project_name} you should implement
|
||||
the relevant changes to adequate the project to PEP 440 immediately.
|
||||
*************************************************************************
|
||||
\n\n!!
|
||||
"""
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
|
||||
return self._parsed_version
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
try:
|
||||
|
|
|
@ -5,25 +5,58 @@ import functools
|
|||
import contextlib
|
||||
import types
|
||||
import importlib
|
||||
import inspect
|
||||
import warnings
|
||||
import itertools
|
||||
|
||||
from typing import Union, Optional
|
||||
from typing import Union, Optional, cast
|
||||
from .abc import ResourceReader, Traversable
|
||||
|
||||
from ._compat import wrap_spec
|
||||
|
||||
Package = Union[types.ModuleType, str]
|
||||
Anchor = Package
|
||||
|
||||
|
||||
def files(package):
|
||||
# type: (Package) -> Traversable
|
||||
def package_to_anchor(func):
|
||||
"""
|
||||
Get a Traversable resource from a package
|
||||
Replace 'package' parameter as 'anchor' and warn about the change.
|
||||
|
||||
Other errors should fall through.
|
||||
|
||||
>>> files('a', 'b')
|
||||
Traceback (most recent call last):
|
||||
TypeError: files() takes from 0 to 1 positional arguments but 2 were given
|
||||
"""
|
||||
return from_package(get_package(package))
|
||||
undefined = object()
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(anchor=undefined, package=undefined):
|
||||
if package is not undefined:
|
||||
if anchor is not undefined:
|
||||
return func(anchor, package)
|
||||
warnings.warn(
|
||||
"First parameter to files is renamed to 'anchor'",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return func(package)
|
||||
elif anchor is undefined:
|
||||
return func()
|
||||
return func(anchor)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_resource_reader(package):
|
||||
# type: (types.ModuleType) -> Optional[ResourceReader]
|
||||
@package_to_anchor
|
||||
def files(anchor: Optional[Anchor] = None) -> Traversable:
|
||||
"""
|
||||
Get a Traversable resource for an anchor.
|
||||
"""
|
||||
return from_package(resolve(anchor))
|
||||
|
||||
|
||||
def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]:
|
||||
"""
|
||||
Return the package's loader if it's a ResourceReader.
|
||||
"""
|
||||
|
@ -39,24 +72,39 @@ def get_resource_reader(package):
|
|||
return reader(spec.name) # type: ignore
|
||||
|
||||
|
||||
def resolve(cand):
|
||||
# type: (Package) -> types.ModuleType
|
||||
return cand if isinstance(cand, types.ModuleType) else importlib.import_module(cand)
|
||||
@functools.singledispatch
|
||||
def resolve(cand: Optional[Anchor]) -> types.ModuleType:
|
||||
return cast(types.ModuleType, cand)
|
||||
|
||||
|
||||
def get_package(package):
|
||||
# type: (Package) -> types.ModuleType
|
||||
"""Take a package name or module object and return the module.
|
||||
@resolve.register
|
||||
def _(cand: str) -> types.ModuleType:
|
||||
return importlib.import_module(cand)
|
||||
|
||||
Raise an exception if the resolved module is not a package.
|
||||
|
||||
@resolve.register
|
||||
def _(cand: None) -> types.ModuleType:
|
||||
return resolve(_infer_caller().f_globals['__name__'])
|
||||
|
||||
|
||||
def _infer_caller():
|
||||
"""
|
||||
resolved = resolve(package)
|
||||
if wrap_spec(resolved).submodule_search_locations is None:
|
||||
raise TypeError(f'{package!r} is not a package')
|
||||
return resolved
|
||||
Walk the stack and find the frame of the first caller not in this module.
|
||||
"""
|
||||
|
||||
def is_this_file(frame_info):
|
||||
return frame_info.filename == __file__
|
||||
|
||||
def is_wrapper(frame_info):
|
||||
return frame_info.function == 'wrapper'
|
||||
|
||||
not_this_file = itertools.filterfalse(is_this_file, inspect.stack())
|
||||
# also exclude 'wrapper' due to singledispatch in the call stack
|
||||
callers = itertools.filterfalse(is_wrapper, not_this_file)
|
||||
return next(callers).frame
|
||||
|
||||
|
||||
def from_package(package):
|
||||
def from_package(package: types.ModuleType):
|
||||
"""
|
||||
Return a Traversable object for the given package.
|
||||
|
||||
|
@ -67,7 +115,14 @@ def from_package(package):
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _tempfile(reader, suffix=''):
|
||||
def _tempfile(
|
||||
reader,
|
||||
suffix='',
|
||||
# gh-93353: Keep a reference to call os.remove() in late Python
|
||||
# finalization.
|
||||
*,
|
||||
_os_remove=os.remove,
|
||||
):
|
||||
# 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.
|
||||
|
@ -81,18 +136,35 @@ def _tempfile(reader, suffix=''):
|
|||
yield pathlib.Path(raw_path)
|
||||
finally:
|
||||
try:
|
||||
os.remove(raw_path)
|
||||
_os_remove(raw_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def _temp_file(path):
|
||||
return _tempfile(path.read_bytes, suffix=path.name)
|
||||
|
||||
|
||||
def _is_present_dir(path: Traversable) -> bool:
|
||||
"""
|
||||
Some Traversables implement ``is_dir()`` to raise an
|
||||
exception (i.e. ``FileNotFoundError``) when the
|
||||
directory doesn't exist. This function wraps that call
|
||||
to always return a boolean and only return True
|
||||
if there's a dir and it exists.
|
||||
"""
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
return path.is_dir()
|
||||
return False
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def as_file(path):
|
||||
"""
|
||||
Given a Traversable object, return that object as a
|
||||
path on the local file system in a context manager.
|
||||
"""
|
||||
return _tempfile(path.read_bytes, suffix=path.name)
|
||||
return _temp_dir(path) if _is_present_dir(path) else _temp_file(path)
|
||||
|
||||
|
||||
@as_file.register(pathlib.Path)
|
||||
|
@ -102,3 +174,34 @@ def _(path):
|
|||
Degenerate behavior for pathlib.Path objects.
|
||||
"""
|
||||
yield path
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _temp_path(dir: tempfile.TemporaryDirectory):
|
||||
"""
|
||||
Wrap tempfile.TemporyDirectory to return a pathlib object.
|
||||
"""
|
||||
with dir as result:
|
||||
yield pathlib.Path(result)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _temp_dir(path):
|
||||
"""
|
||||
Given a traversable dir, recursively replicate the whole tree
|
||||
to the file system in a context manager.
|
||||
"""
|
||||
assert path.is_dir()
|
||||
with _temp_path(tempfile.TemporaryDirectory()) as temp_dir:
|
||||
yield _write_contents(temp_dir, path)
|
||||
|
||||
|
||||
def _write_contents(target, source):
|
||||
child = target.joinpath(source.name)
|
||||
if source.is_dir():
|
||||
child.mkdir()
|
||||
for item in source.iterdir():
|
||||
_write_contents(child, item)
|
||||
else:
|
||||
child.write_bytes(source.read_bytes())
|
||||
return child
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
# flake8: noqa
|
||||
|
||||
import abc
|
||||
import os
|
||||
import sys
|
||||
import pathlib
|
||||
from contextlib import suppress
|
||||
from typing import Union
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from zipfile import Path as ZipPath # type: ignore
|
||||
|
@ -96,3 +99,10 @@ def wrap_spec(package):
|
|||
from . import _adapters
|
||||
|
||||
return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
else:
|
||||
# PathLike is only subscriptable at runtime in 3.9+
|
||||
StrPath = Union[str, "os.PathLike[str]"]
|
||||
|
|
|
@ -27,8 +27,7 @@ def deprecated(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
def normalize_path(path):
|
||||
# type: (Any) -> str
|
||||
def normalize_path(path: Any) -> str:
|
||||
"""Normalize a path by ensuring it is a string.
|
||||
|
||||
If the resulting string contains path separators, an exception is raised.
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import abc
|
||||
from typing import BinaryIO, Iterable, Text
|
||||
import io
|
||||
import itertools
|
||||
import pathlib
|
||||
from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional
|
||||
|
||||
from ._compat import runtime_checkable, Protocol
|
||||
from ._compat import runtime_checkable, Protocol, StrPath
|
||||
|
||||
|
||||
__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
|
||||
|
||||
|
||||
class ResourceReader(metaclass=abc.ABCMeta):
|
||||
|
@ -46,27 +52,34 @@ class ResourceReader(metaclass=abc.ABCMeta):
|
|||
raise FileNotFoundError
|
||||
|
||||
|
||||
class TraversalError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Traversable(Protocol):
|
||||
"""
|
||||
An object with a subset of pathlib.Path methods suitable for
|
||||
traversing directories and opening files.
|
||||
|
||||
Any exceptions that occur when accessing the backing resource
|
||||
may propagate unaltered.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def iterdir(self):
|
||||
def iterdir(self) -> Iterator["Traversable"]:
|
||||
"""
|
||||
Yield Traversable objects in self
|
||||
"""
|
||||
|
||||
def read_bytes(self):
|
||||
def read_bytes(self) -> bytes:
|
||||
"""
|
||||
Read contents of self as bytes
|
||||
"""
|
||||
with self.open('rb') as strm:
|
||||
return strm.read()
|
||||
|
||||
def read_text(self, encoding=None):
|
||||
def read_text(self, encoding: Optional[str] = None) -> str:
|
||||
"""
|
||||
Read contents of self as text
|
||||
"""
|
||||
|
@ -85,13 +98,32 @@ class Traversable(Protocol):
|
|||
Return True if self is a file
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def joinpath(self, child):
|
||||
"""
|
||||
Return Traversable child in self
|
||||
def joinpath(self, *descendants: StrPath) -> "Traversable":
|
||||
"""
|
||||
Return Traversable resolved with any descendants applied.
|
||||
|
||||
def __truediv__(self, child):
|
||||
Each descendant should be a path segment relative to self
|
||||
and each may contain multiple levels separated by
|
||||
``posixpath.sep`` (``/``).
|
||||
"""
|
||||
if not descendants:
|
||||
return self
|
||||
names = itertools.chain.from_iterable(
|
||||
path.parts for path in map(pathlib.PurePosixPath, descendants)
|
||||
)
|
||||
target = next(names)
|
||||
matches = (
|
||||
traversable for traversable in self.iterdir() if traversable.name == target
|
||||
)
|
||||
try:
|
||||
match = next(matches)
|
||||
except StopIteration:
|
||||
raise TraversalError(
|
||||
"Target not found during traversal.", target, list(names)
|
||||
)
|
||||
return match.joinpath(*names)
|
||||
|
||||
def __truediv__(self, child: StrPath) -> "Traversable":
|
||||
"""
|
||||
Return Traversable child in self
|
||||
"""
|
||||
|
@ -107,7 +139,8 @@ class Traversable(Protocol):
|
|||
accepted by io.TextIOWrapper.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def name(self) -> str:
|
||||
"""
|
||||
The base name of this object without any parent references.
|
||||
|
@ -121,17 +154,17 @@ class TraversableResources(ResourceReader):
|
|||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def files(self):
|
||||
def files(self) -> "Traversable":
|
||||
"""Return a Traversable object for the loaded package."""
|
||||
|
||||
def open_resource(self, resource):
|
||||
def open_resource(self, resource: StrPath) -> io.BufferedReader:
|
||||
return self.files().joinpath(resource).open('rb')
|
||||
|
||||
def resource_path(self, resource):
|
||||
def resource_path(self, resource: Any) -> NoReturn:
|
||||
raise FileNotFoundError(resource)
|
||||
|
||||
def is_resource(self, path):
|
||||
def is_resource(self, path: StrPath) -> bool:
|
||||
return self.files().joinpath(path).is_file()
|
||||
|
||||
def contents(self):
|
||||
def contents(self) -> Iterator[str]:
|
||||
return (item.name for item in self.files().iterdir())
|
||||
|
|
|
@ -82,15 +82,13 @@ class MultiplexedPath(abc.Traversable):
|
|||
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 joinpath(self, *descendants):
|
||||
try:
|
||||
return super().joinpath(*descendants)
|
||||
except abc.TraversalError:
|
||||
# One of the paths did not resolve (a directory does not exist).
|
||||
# Just return something that will not exist.
|
||||
return self._paths[0].joinpath(*descendants)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
raise FileNotFoundError(f'{self} is not a file')
|
||||
|
|
|
@ -16,31 +16,28 @@ class SimpleReader(abc.ABC):
|
|||
provider.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def package(self):
|
||||
# type: () -> str
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def package(self) -> str:
|
||||
"""
|
||||
The name of the package for which this reader loads resources.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def children(self):
|
||||
# type: () -> List['SimpleReader']
|
||||
def children(self) -> List['SimpleReader']:
|
||||
"""
|
||||
Obtain an iterable of SimpleReader for available
|
||||
child containers (e.g. directories).
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def resources(self):
|
||||
# type: () -> List[str]
|
||||
def resources(self) -> List[str]:
|
||||
"""
|
||||
Obtain available named resources for this virtual package.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def open_binary(self, resource):
|
||||
# type: (str) -> BinaryIO
|
||||
def open_binary(self, resource: str) -> BinaryIO:
|
||||
"""
|
||||
Obtain a File-like for a named resource.
|
||||
"""
|
||||
|
@ -50,13 +47,35 @@ class SimpleReader(abc.ABC):
|
|||
return self.package.split('.')[-1]
|
||||
|
||||
|
||||
class ResourceContainer(Traversable):
|
||||
"""
|
||||
Traversable container for a package's resources via its reader.
|
||||
"""
|
||||
|
||||
def __init__(self, reader: SimpleReader):
|
||||
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()
|
||||
|
||||
|
||||
class ResourceHandle(Traversable):
|
||||
"""
|
||||
Handle to a named resource in a ResourceReader.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, name):
|
||||
# type: (ResourceContainer, str) -> None
|
||||
def __init__(self, parent: ResourceContainer, name: str):
|
||||
self.parent = parent
|
||||
self.name = name # type: ignore
|
||||
|
||||
|
@ -76,35 +95,6 @@ class ResourceHandle(Traversable):
|
|||
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
|
||||
|
|
|
@ -6,6 +6,6 @@ typing_extensions==4.4.0
|
|||
|
||||
jaraco.text==3.7.0
|
||||
# required for jaraco.text on older Pythons
|
||||
importlib_resources==5.4.0
|
||||
importlib_resources==5.10.2
|
||||
# required for importlib_resources on older Pythons
|
||||
zipp==3.7.0
|
||||
|
|
Loading…
Reference in a new issue