mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-21 05:43:16 -07:00
add dependency
This commit is contained in:
parent
c8864bdf2f
commit
932ad40146
46 changed files with 2207 additions and 4 deletions
|
@ -13,8 +13,6 @@ jobs:
|
||||||
vmImage: 'Ubuntu-latest'
|
vmImage: 'Ubuntu-latest'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Python37:
|
|
||||||
python.version: '3.7'
|
|
||||||
Python38:
|
Python38:
|
||||||
python.version: '3.8'
|
python.version: '3.8'
|
||||||
Python39:
|
Python39:
|
||||||
|
|
1
eol.py
1
eol.py
|
@ -28,6 +28,7 @@ def date(string, fmt='%Y-%m-%d'):
|
||||||
# https://devguide.python.org/
|
# https://devguide.python.org/
|
||||||
# https://devguide.python.org/devcycle/#devcycle
|
# https://devguide.python.org/devcycle/#devcycle
|
||||||
PYTHON_EOL = {
|
PYTHON_EOL = {
|
||||||
|
(3, 13): date('2029-10-1'),
|
||||||
(3, 12): date('2028-10-1'),
|
(3, 12): date('2028-10-1'),
|
||||||
(3, 11): date('2027-10-1'),
|
(3, 11): date('2027-10-1'),
|
||||||
(3, 10): date('2026-10-01'),
|
(3, 10): date('2026-10-01'),
|
||||||
|
|
17
libs/common/importlib_resources/__init__.py
Normal file
17
libs/common/importlib_resources/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
"""Read resources contained within a package."""
|
||||||
|
|
||||||
|
from ._common import (
|
||||||
|
as_file,
|
||||||
|
files,
|
||||||
|
Package,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .abc import ResourceReader
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Package',
|
||||||
|
'ResourceReader',
|
||||||
|
'as_file',
|
||||||
|
'files',
|
||||||
|
]
|
168
libs/common/importlib_resources/_adapters.py
Normal file
168
libs/common/importlib_resources/_adapters.py
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
from contextlib import suppress
|
||||||
|
from io import TextIOWrapper
|
||||||
|
|
||||||
|
from . import abc
|
||||||
|
|
||||||
|
|
||||||
|
class SpecLoaderAdapter:
|
||||||
|
"""
|
||||||
|
Adapt a package spec to adapt the underlying loader.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, spec, adapter=lambda spec: spec.loader):
|
||||||
|
self.spec = spec
|
||||||
|
self.loader = adapter(spec)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.spec, name)
|
||||||
|
|
||||||
|
|
||||||
|
class TraversableResourcesLoader:
|
||||||
|
"""
|
||||||
|
Adapt a loader to provide TraversableResources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, spec):
|
||||||
|
self.spec = spec
|
||||||
|
|
||||||
|
def get_resource_reader(self, name):
|
||||||
|
return CompatibilityFiles(self.spec)._native()
|
||||||
|
|
||||||
|
|
||||||
|
def _io_wrapper(file, mode='r', *args, **kwargs):
|
||||||
|
if mode == 'r':
|
||||||
|
return TextIOWrapper(file, *args, **kwargs)
|
||||||
|
elif mode == 'rb':
|
||||||
|
return file
|
||||||
|
raise ValueError(f"Invalid mode value '{mode}', only 'r' and 'rb' are supported")
|
||||||
|
|
||||||
|
|
||||||
|
class CompatibilityFiles:
|
||||||
|
"""
|
||||||
|
Adapter for an existing or non-existent resource reader
|
||||||
|
to provide a compatibility .files().
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SpecPath(abc.Traversable):
|
||||||
|
"""
|
||||||
|
Path tied to a module spec.
|
||||||
|
Can be read and exposes the resource reader children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, spec, reader):
|
||||||
|
self._spec = spec
|
||||||
|
self._reader = reader
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
if not self._reader:
|
||||||
|
return iter(())
|
||||||
|
return iter(
|
||||||
|
CompatibilityFiles.ChildPath(self._reader, path)
|
||||||
|
for path in self._reader.contents()
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
is_dir = is_file
|
||||||
|
|
||||||
|
def joinpath(self, other):
|
||||||
|
if not self._reader:
|
||||||
|
return CompatibilityFiles.OrphanPath(other)
|
||||||
|
return CompatibilityFiles.ChildPath(self._reader, other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._spec.name
|
||||||
|
|
||||||
|
def open(self, mode='r', *args, **kwargs):
|
||||||
|
return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs)
|
||||||
|
|
||||||
|
class ChildPath(abc.Traversable):
|
||||||
|
"""
|
||||||
|
Path tied to a resource reader child.
|
||||||
|
Can be read but doesn't expose any meaningful children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, reader, name):
|
||||||
|
self._reader = reader
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return self._reader.is_resource(self.name)
|
||||||
|
|
||||||
|
def is_dir(self):
|
||||||
|
return not self.is_file()
|
||||||
|
|
||||||
|
def joinpath(self, other):
|
||||||
|
return CompatibilityFiles.OrphanPath(self.name, other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def open(self, mode='r', *args, **kwargs):
|
||||||
|
return _io_wrapper(
|
||||||
|
self._reader.open_resource(self.name), mode, *args, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
class OrphanPath(abc.Traversable):
|
||||||
|
"""
|
||||||
|
Orphan path, not tied to a module spec or resource reader.
|
||||||
|
Can't be read and doesn't expose any meaningful children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *path_parts):
|
||||||
|
if len(path_parts) < 1:
|
||||||
|
raise ValueError('Need at least one path part to construct a path')
|
||||||
|
self._path = path_parts
|
||||||
|
|
||||||
|
def iterdir(self):
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
def is_file(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
is_dir = is_file
|
||||||
|
|
||||||
|
def joinpath(self, other):
|
||||||
|
return CompatibilityFiles.OrphanPath(*self._path, other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._path[-1]
|
||||||
|
|
||||||
|
def open(self, mode='r', *args, **kwargs):
|
||||||
|
raise FileNotFoundError("Can't open orphan path")
|
||||||
|
|
||||||
|
def __init__(self, spec):
|
||||||
|
self.spec = spec
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _reader(self):
|
||||||
|
with suppress(AttributeError):
|
||||||
|
return self.spec.loader.get_resource_reader(self.spec.name)
|
||||||
|
|
||||||
|
def _native(self):
|
||||||
|
"""
|
||||||
|
Return the native reader if it supports files().
|
||||||
|
"""
|
||||||
|
reader = self._reader
|
||||||
|
return reader if hasattr(reader, 'files') else self
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self._reader, attr)
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return CompatibilityFiles.SpecPath(self.spec, self._reader)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_spec(package):
|
||||||
|
"""
|
||||||
|
Construct a package spec with traversable compatibility
|
||||||
|
on the spec/loader/reader.
|
||||||
|
"""
|
||||||
|
return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
|
209
libs/common/importlib_resources/_common.py
Normal file
209
libs/common/importlib_resources/_common.py
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import tempfile
|
||||||
|
import functools
|
||||||
|
import contextlib
|
||||||
|
import types
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import warnings
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from typing import Union, Optional, cast
|
||||||
|
from .abc import ResourceReader, Traversable
|
||||||
|
|
||||||
|
from .future.adapters import wrap_spec
|
||||||
|
|
||||||
|
Package = Union[types.ModuleType, str]
|
||||||
|
Anchor = Package
|
||||||
|
|
||||||
|
|
||||||
|
def package_to_anchor(func):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
Remove this compatibility in Python 3.14.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@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.
|
||||||
|
"""
|
||||||
|
# We can't use
|
||||||
|
# a issubclass() check here because apparently abc.'s __subclasscheck__()
|
||||||
|
# hook wants to create a weak reference to the object, but
|
||||||
|
# zipimport.zipimporter does not support weak references, resulting in a
|
||||||
|
# TypeError. That seems terrible.
|
||||||
|
spec = package.__spec__
|
||||||
|
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore
|
||||||
|
if reader is None:
|
||||||
|
return None
|
||||||
|
return reader(spec.name) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@functools.singledispatch
|
||||||
|
def resolve(cand: Optional[Anchor]) -> types.ModuleType:
|
||||||
|
return cast(types.ModuleType, cand)
|
||||||
|
|
||||||
|
|
||||||
|
@resolve.register
|
||||||
|
def _(cand: str) -> types.ModuleType:
|
||||||
|
return importlib.import_module(cand)
|
||||||
|
|
||||||
|
|
||||||
|
@resolve.register
|
||||||
|
def _(cand: None) -> types.ModuleType:
|
||||||
|
return resolve(_infer_caller().f_globals['__name__'])
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_caller():
|
||||||
|
"""
|
||||||
|
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: types.ModuleType):
|
||||||
|
"""
|
||||||
|
Return a Traversable object for the given package.
|
||||||
|
|
||||||
|
"""
|
||||||
|
spec = wrap_spec(package)
|
||||||
|
reader = spec.loader.get_resource_reader(spec.name)
|
||||||
|
return reader.files()
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
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.
|
||||||
|
fd, raw_path = tempfile.mkstemp(suffix=suffix)
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
os.write(fd, reader())
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
del reader
|
||||||
|
yield pathlib.Path(raw_path)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
_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 _temp_dir(path) if _is_present_dir(path) else _temp_file(path)
|
||||||
|
|
||||||
|
|
||||||
|
@as_file.register(pathlib.Path)
|
||||||
|
@contextlib.contextmanager
|
||||||
|
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
|
38
libs/common/importlib_resources/_itertools.py
Normal file
38
libs/common/importlib_resources/_itertools.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# from more_itertools 9.0
|
||||||
|
def only(iterable, default=None, too_long=None):
|
||||||
|
"""If *iterable* has only one item, return it.
|
||||||
|
If it has zero items, return *default*.
|
||||||
|
If it has more than one item, raise the exception given by *too_long*,
|
||||||
|
which is ``ValueError`` by default.
|
||||||
|
>>> only([], default='missing')
|
||||||
|
'missing'
|
||||||
|
>>> only([1])
|
||||||
|
1
|
||||||
|
>>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: Expected exactly one item in iterable, but got 1, 2,
|
||||||
|
and perhaps more.'
|
||||||
|
>>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError
|
||||||
|
Note that :func:`only` attempts to advance *iterable* twice to ensure there
|
||||||
|
is only one item. See :func:`spy` or :func:`peekable` to check
|
||||||
|
iterable contents less destructively.
|
||||||
|
"""
|
||||||
|
it = iter(iterable)
|
||||||
|
first_value = next(it, default)
|
||||||
|
|
||||||
|
try:
|
||||||
|
second_value = next(it)
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
'Expected exactly one item in iterable, but got {!r}, {!r}, '
|
||||||
|
'and perhaps more.'.format(first_value, second_value)
|
||||||
|
)
|
||||||
|
raise too_long or ValueError(msg)
|
||||||
|
|
||||||
|
return first_value
|
171
libs/common/importlib_resources/abc.py
Normal file
171
libs/common/importlib_resources/abc.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import abc
|
||||||
|
import io
|
||||||
|
import itertools
|
||||||
|
import pathlib
|
||||||
|
from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional
|
||||||
|
from typing import runtime_checkable, Protocol
|
||||||
|
|
||||||
|
from .compat.py38 import StrPath
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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) -> Iterator["Traversable"]:
|
||||||
|
"""
|
||||||
|
Yield Traversable objects in 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: Optional[str] = None) -> str:
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
def joinpath(self, *descendants: StrPath) -> "Traversable":
|
||||||
|
"""
|
||||||
|
Return Traversable resolved with any descendants applied.
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
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) -> "Traversable":
|
||||||
|
"""Return a Traversable object for the loaded package."""
|
||||||
|
|
||||||
|
def open_resource(self, resource: StrPath) -> io.BufferedReader:
|
||||||
|
return self.files().joinpath(resource).open('rb')
|
||||||
|
|
||||||
|
def resource_path(self, resource: Any) -> NoReturn:
|
||||||
|
raise FileNotFoundError(resource)
|
||||||
|
|
||||||
|
def is_resource(self, path: StrPath) -> bool:
|
||||||
|
return self.files().joinpath(path).is_file()
|
||||||
|
|
||||||
|
def contents(self) -> Iterator[str]:
|
||||||
|
return (item.name for item in self.files().iterdir())
|
0
libs/common/importlib_resources/compat/__init__.py
Normal file
0
libs/common/importlib_resources/compat/__init__.py
Normal file
11
libs/common/importlib_resources/compat/py38.py
Normal file
11
libs/common/importlib_resources/compat/py38.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
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]"]
|
10
libs/common/importlib_resources/compat/py39.py
Normal file
10
libs/common/importlib_resources/compat/py39.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['ZipPath']
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
from zipfile import Path as ZipPath # type: ignore
|
||||||
|
else:
|
||||||
|
from zipp import Path as ZipPath # type: ignore
|
0
libs/common/importlib_resources/future/__init__.py
Normal file
0
libs/common/importlib_resources/future/__init__.py
Normal file
46
libs/common/importlib_resources/future/adapters.py
Normal file
46
libs/common/importlib_resources/future/adapters.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import pathlib
|
||||||
|
from contextlib import suppress
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from .. import readers, _adapters
|
||||||
|
|
||||||
|
|
||||||
|
class TraversableResourcesLoader(_adapters.TraversableResourcesLoader):
|
||||||
|
"""
|
||||||
|
Adapt loaders to provide TraversableResources and other
|
||||||
|
compatibility.
|
||||||
|
|
||||||
|
Ensures the readers from importlib_resources are preferred
|
||||||
|
over stdlib readers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_resource_reader(self, name):
|
||||||
|
return self._standard_reader() or super().get_resource_reader(name)
|
||||||
|
|
||||||
|
def _standard_reader(self):
|
||||||
|
return self._zip_reader() or self._namespace_reader() or self._file_reader()
|
||||||
|
|
||||||
|
def _zip_reader(self):
|
||||||
|
with suppress(AttributeError):
|
||||||
|
return readers.ZipReader(self.spec.loader, self.spec.name)
|
||||||
|
|
||||||
|
def _namespace_reader(self):
|
||||||
|
with suppress(AttributeError, ValueError):
|
||||||
|
return readers.NamespaceReader(self.spec.submodule_search_locations)
|
||||||
|
|
||||||
|
def _file_reader(self):
|
||||||
|
try:
|
||||||
|
path = pathlib.Path(self.spec.origin)
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
if path.exists():
|
||||||
|
return readers.FileReader(SimpleNamespace(path=path))
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_spec(package):
|
||||||
|
"""
|
||||||
|
Override _adapters.wrap_spec to use TraversableResourcesLoader
|
||||||
|
from above. Ensures that future behavior is always available on older
|
||||||
|
Pythons.
|
||||||
|
"""
|
||||||
|
return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
|
0
libs/common/importlib_resources/py.typed
Normal file
0
libs/common/importlib_resources/py.typed
Normal file
194
libs/common/importlib_resources/readers.py
Normal file
194
libs/common/importlib_resources/readers.py
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
import collections
|
||||||
|
import contextlib
|
||||||
|
import itertools
|
||||||
|
import pathlib
|
||||||
|
import operator
|
||||||
|
import re
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from . import abc
|
||||||
|
|
||||||
|
from ._itertools import only
|
||||||
|
from .compat.py39 import ZipPath
|
||||||
|
|
||||||
|
|
||||||
|
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 ZipPath(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(_ensure_traversable, 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):
|
||||||
|
children = (child for path in self._paths for child in path.iterdir())
|
||||||
|
by_name = operator.attrgetter('name')
|
||||||
|
groups = itertools.groupby(sorted(children, key=by_name), key=by_name)
|
||||||
|
return map(self._follow, (locs for name, locs in groups))
|
||||||
|
|
||||||
|
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, *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)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _follow(cls, children):
|
||||||
|
"""
|
||||||
|
Construct a MultiplexedPath if needed.
|
||||||
|
|
||||||
|
If children contains a sole element, return it.
|
||||||
|
Otherwise, return a MultiplexedPath of the items.
|
||||||
|
Unless one of the items is not a Directory, then return the first.
|
||||||
|
"""
|
||||||
|
subdirs, one_dir, one_file = itertools.tee(children, 3)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return only(one_dir)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
return cls(*subdirs)
|
||||||
|
except NotADirectoryError:
|
||||||
|
return next(one_file)
|
||||||
|
|
||||||
|
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(*map(self._resolve, namespace_path))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _resolve(cls, path_str) -> abc.Traversable:
|
||||||
|
r"""
|
||||||
|
Given an item from a namespace path, resolve it to a Traversable.
|
||||||
|
|
||||||
|
path_str might be a directory on the filesystem or a path to a
|
||||||
|
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
|
||||||
|
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
|
||||||
|
"""
|
||||||
|
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
|
||||||
|
return dir
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _candidate_paths(cls, path_str):
|
||||||
|
yield pathlib.Path(path_str)
|
||||||
|
yield from cls._resolve_zip_path(path_str)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_zip_path(path_str):
|
||||||
|
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
|
||||||
|
with contextlib.suppress(
|
||||||
|
FileNotFoundError,
|
||||||
|
IsADirectoryError,
|
||||||
|
NotADirectoryError,
|
||||||
|
PermissionError,
|
||||||
|
):
|
||||||
|
inner = path_str[match.end() :].replace('\\', '/') + '/'
|
||||||
|
yield ZipPath(path_str[: match.start()], inner.lstrip('/'))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_traversable(path):
|
||||||
|
"""
|
||||||
|
Convert deprecated string arguments to traversables (pathlib.Path).
|
||||||
|
|
||||||
|
Remove with Python 3.15.
|
||||||
|
"""
|
||||||
|
if not isinstance(path, str):
|
||||||
|
return path
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"String arguments are deprecated. Pass a Traversable instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
return pathlib.Path(path)
|
106
libs/common/importlib_resources/simple.py
Normal file
106
libs/common/importlib_resources/simple.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def package(self) -> str:
|
||||||
|
"""
|
||||||
|
The name of the package for which this reader loads resources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def children(self) -> List['SimpleReader']:
|
||||||
|
"""
|
||||||
|
Obtain an iterable of SimpleReader for available
|
||||||
|
child containers (e.g. directories).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def resources(self) -> List[str]:
|
||||||
|
"""
|
||||||
|
Obtain available named resources for this virtual package.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def open_binary(self, resource: str) -> BinaryIO:
|
||||||
|
"""
|
||||||
|
Obtain a File-like for a named resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
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: ResourceContainer, name: str):
|
||||||
|
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(stream, *args, **kwargs)
|
||||||
|
return stream
|
||||||
|
|
||||||
|
def joinpath(self, name):
|
||||||
|
raise RuntimeError("Cannot traverse into a resource")
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
0
libs/common/importlib_resources/tests/__init__.py
Normal file
0
libs/common/importlib_resources/tests/__init__.py
Normal file
32
libs/common/importlib_resources/tests/_compat.py
Normal file
32
libs/common/importlib_resources/tests/_compat.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from test.support import import_helper # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# Python 3.9 and earlier
|
||||||
|
class import_helper: # type: ignore
|
||||||
|
from test.support import (
|
||||||
|
modules_setup,
|
||||||
|
modules_cleanup,
|
||||||
|
DirsOnSysPath,
|
||||||
|
CleanImport,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from test.support import os_helper # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# Python 3.9 compat
|
||||||
|
class os_helper: # type:ignore
|
||||||
|
from test.support import temp_dir
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Python 3.10
|
||||||
|
from test.support.os_helper import unlink
|
||||||
|
except ImportError:
|
||||||
|
from test.support import unlink as _unlink
|
||||||
|
|
||||||
|
def unlink(target):
|
||||||
|
return _unlink(os.fspath(target))
|
56
libs/common/importlib_resources/tests/_path.py
Normal file
56
libs/common/importlib_resources/tests/_path.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import pathlib
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from typing import Dict, Union
|
||||||
|
|
||||||
|
|
||||||
|
####
|
||||||
|
# from jaraco.path 3.4.1
|
||||||
|
|
||||||
|
FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def build(spec: FilesSpec, prefix=pathlib.Path()):
|
||||||
|
"""
|
||||||
|
Build a set of files/directories, as described by the spec.
|
||||||
|
|
||||||
|
Each key represents a pathname, and the value represents
|
||||||
|
the content. Content may be a nested directory.
|
||||||
|
|
||||||
|
>>> spec = {
|
||||||
|
... 'README.txt': "A README file",
|
||||||
|
... "foo": {
|
||||||
|
... "__init__.py": "",
|
||||||
|
... "bar": {
|
||||||
|
... "__init__.py": "",
|
||||||
|
... },
|
||||||
|
... "baz.py": "# Some code",
|
||||||
|
... }
|
||||||
|
... }
|
||||||
|
>>> target = getfixture('tmp_path')
|
||||||
|
>>> build(spec, target)
|
||||||
|
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
|
||||||
|
'# Some code'
|
||||||
|
"""
|
||||||
|
for name, contents in spec.items():
|
||||||
|
create(contents, pathlib.Path(prefix) / name)
|
||||||
|
|
||||||
|
|
||||||
|
@functools.singledispatch
|
||||||
|
def create(content: Union[str, bytes, FilesSpec], path):
|
||||||
|
path.mkdir(exist_ok=True)
|
||||||
|
build(content, prefix=path) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@create.register
|
||||||
|
def _(content: bytes, path):
|
||||||
|
path.write_bytes(content)
|
||||||
|
|
||||||
|
|
||||||
|
@create.register
|
||||||
|
def _(content: str, path):
|
||||||
|
path.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
# end from jaraco.path
|
||||||
|
####
|
0
libs/common/importlib_resources/tests/data01/__init__.py
Normal file
0
libs/common/importlib_resources/tests/data01/__init__.py
Normal file
BIN
libs/common/importlib_resources/tests/data01/binary.file
Normal file
BIN
libs/common/importlib_resources/tests/data01/binary.file
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
|
BIN
libs/common/importlib_resources/tests/data01/utf-16.file
Normal file
BIN
libs/common/importlib_resources/tests/data01/utf-16.file
Normal file
Binary file not shown.
1
libs/common/importlib_resources/tests/data01/utf-8.file
Normal file
1
libs/common/importlib_resources/tests/data01/utf-8.file
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello, UTF-8 world!
|
0
libs/common/importlib_resources/tests/data02/__init__.py
Normal file
0
libs/common/importlib_resources/tests/data02/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
one resource
|
|
@ -0,0 +1 @@
|
||||||
|
a resource
|
|
@ -0,0 +1 @@
|
||||||
|
two resource
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
Hello, UTF-8 world!
|
104
libs/common/importlib_resources/tests/test_compatibilty_files.py
Normal file
104
libs/common/importlib_resources/tests/test_compatibilty_files.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import io
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import importlib_resources as resources
|
||||||
|
|
||||||
|
from importlib_resources._adapters import (
|
||||||
|
CompatibilityFiles,
|
||||||
|
wrap_spec,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
class CompatibilityFilesTests(unittest.TestCase):
|
||||||
|
@property
|
||||||
|
def package(self):
|
||||||
|
bytes_data = io.BytesIO(b'Hello, world!')
|
||||||
|
return util.create_package(
|
||||||
|
file=bytes_data,
|
||||||
|
path='some_path',
|
||||||
|
contents=('a', 'b', 'c'),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
return resources.files(self.package)
|
||||||
|
|
||||||
|
def test_spec_path_iter(self):
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(path.name for path in self.files.iterdir()),
|
||||||
|
['a', 'b', 'c'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_child_path_iter(self):
|
||||||
|
self.assertEqual(list((self.files / 'a').iterdir()), [])
|
||||||
|
|
||||||
|
def test_orphan_path_iter(self):
|
||||||
|
self.assertEqual(list((self.files / 'a' / 'a').iterdir()), [])
|
||||||
|
self.assertEqual(list((self.files / 'a' / 'a' / 'a').iterdir()), [])
|
||||||
|
|
||||||
|
def test_spec_path_is(self):
|
||||||
|
self.assertFalse(self.files.is_file())
|
||||||
|
self.assertFalse(self.files.is_dir())
|
||||||
|
|
||||||
|
def test_child_path_is(self):
|
||||||
|
self.assertTrue((self.files / 'a').is_file())
|
||||||
|
self.assertFalse((self.files / 'a').is_dir())
|
||||||
|
|
||||||
|
def test_orphan_path_is(self):
|
||||||
|
self.assertFalse((self.files / 'a' / 'a').is_file())
|
||||||
|
self.assertFalse((self.files / 'a' / 'a').is_dir())
|
||||||
|
self.assertFalse((self.files / 'a' / 'a' / 'a').is_file())
|
||||||
|
self.assertFalse((self.files / 'a' / 'a' / 'a').is_dir())
|
||||||
|
|
||||||
|
def test_spec_path_name(self):
|
||||||
|
self.assertEqual(self.files.name, 'testingpackage')
|
||||||
|
|
||||||
|
def test_child_path_name(self):
|
||||||
|
self.assertEqual((self.files / 'a').name, 'a')
|
||||||
|
|
||||||
|
def test_orphan_path_name(self):
|
||||||
|
self.assertEqual((self.files / 'a' / 'b').name, 'b')
|
||||||
|
self.assertEqual((self.files / 'a' / 'b' / 'c').name, 'c')
|
||||||
|
|
||||||
|
def test_spec_path_open(self):
|
||||||
|
self.assertEqual(self.files.read_bytes(), b'Hello, world!')
|
||||||
|
self.assertEqual(self.files.read_text(encoding='utf-8'), 'Hello, world!')
|
||||||
|
|
||||||
|
def test_child_path_open(self):
|
||||||
|
self.assertEqual((self.files / 'a').read_bytes(), b'Hello, world!')
|
||||||
|
self.assertEqual(
|
||||||
|
(self.files / 'a').read_text(encoding='utf-8'), 'Hello, world!'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_orphan_path_open(self):
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
(self.files / 'a' / 'b').read_bytes()
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
(self.files / 'a' / 'b' / 'c').read_bytes()
|
||||||
|
|
||||||
|
def test_open_invalid_mode(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.files.open('0')
|
||||||
|
|
||||||
|
def test_orphan_path_invalid(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
CompatibilityFiles.OrphanPath()
|
||||||
|
|
||||||
|
def test_wrap_spec(self):
|
||||||
|
spec = wrap_spec(self.package)
|
||||||
|
self.assertIsInstance(spec.loader.get_resource_reader(None), CompatibilityFiles)
|
||||||
|
|
||||||
|
|
||||||
|
class CompatibilityFilesNoReaderTests(unittest.TestCase):
|
||||||
|
@property
|
||||||
|
def package(self):
|
||||||
|
return util.create_package_from_loader(None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def files(self):
|
||||||
|
return resources.files(self.package)
|
||||||
|
|
||||||
|
def test_spec_path_joinpath(self):
|
||||||
|
self.assertIsInstance(self.files / 'a', CompatibilityFiles.OrphanPath)
|
43
libs/common/importlib_resources/tests/test_contents.py
Normal file
43
libs/common/importlib_resources/tests/test_contents.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import unittest
|
||||||
|
import importlib_resources as resources
|
||||||
|
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
class ContentsTests:
|
||||||
|
expected = {
|
||||||
|
'__init__.py',
|
||||||
|
'binary.file',
|
||||||
|
'subdirectory',
|
||||||
|
'utf-16.file',
|
||||||
|
'utf-8.file',
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_contents(self):
|
||||||
|
contents = {path.name for path in resources.files(self.data).iterdir()}
|
||||||
|
assert self.expected <= contents
|
||||||
|
|
||||||
|
|
||||||
|
class ContentsDiskTests(ContentsTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = data01
|
||||||
|
|
||||||
|
|
||||||
|
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
|
||||||
|
expected = {
|
||||||
|
# no __init__ because of namespace design
|
||||||
|
'binary.file',
|
||||||
|
'subdirectory',
|
||||||
|
'utf-16.file',
|
||||||
|
'utf-8.file',
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
from . import namespacedata01
|
||||||
|
|
||||||
|
self.data = namespacedata01
|
47
libs/common/importlib_resources/tests/test_custom.py
Normal file
47
libs/common/importlib_resources/tests/test_custom.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import unittest
|
||||||
|
import contextlib
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import importlib_resources as resources
|
||||||
|
from .. import abc
|
||||||
|
from ..abc import TraversableResources, ResourceReader
|
||||||
|
from . import util
|
||||||
|
from ._compat import os_helper
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleLoader:
|
||||||
|
"""
|
||||||
|
A simple loader that only implements a resource reader.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, reader: ResourceReader):
|
||||||
|
self.reader = reader
|
||||||
|
|
||||||
|
def get_resource_reader(self, package):
|
||||||
|
return self.reader
|
||||||
|
|
||||||
|
|
||||||
|
class MagicResources(TraversableResources):
|
||||||
|
"""
|
||||||
|
Magically returns the resources at path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: pathlib.Path):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
class CustomTraversableResourcesTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.fixtures = contextlib.ExitStack()
|
||||||
|
self.addCleanup(self.fixtures.close)
|
||||||
|
|
||||||
|
def test_custom_loader(self):
|
||||||
|
temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir()))
|
||||||
|
loader = SimpleLoader(MagicResources(temp_dir))
|
||||||
|
pkg = util.create_package_from_loader(loader)
|
||||||
|
files = resources.files(pkg)
|
||||||
|
assert isinstance(files, abc.Traversable)
|
||||||
|
assert list(files.iterdir()) == []
|
111
libs/common/importlib_resources/tests/test_files.py
Normal file
111
libs/common/importlib_resources/tests/test_files.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import textwrap
|
||||||
|
import unittest
|
||||||
|
import warnings
|
||||||
|
import importlib
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import importlib_resources as resources
|
||||||
|
from ..abc import Traversable
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
from . import _path
|
||||||
|
from ._compat import os_helper, import_helper
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def suppress_known_deprecation():
|
||||||
|
with warnings.catch_warnings(record=True) as ctx:
|
||||||
|
warnings.simplefilter('default', category=DeprecationWarning)
|
||||||
|
yield ctx
|
||||||
|
|
||||||
|
|
||||||
|
class FilesTests:
|
||||||
|
def test_read_bytes(self):
|
||||||
|
files = resources.files(self.data)
|
||||||
|
actual = files.joinpath('utf-8.file').read_bytes()
|
||||||
|
assert actual == b'Hello, UTF-8 world!\n'
|
||||||
|
|
||||||
|
def test_read_text(self):
|
||||||
|
files = resources.files(self.data)
|
||||||
|
actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
|
||||||
|
assert actual == 'Hello, UTF-8 world!\n'
|
||||||
|
|
||||||
|
def test_traversable(self):
|
||||||
|
assert isinstance(resources.files(self.data), Traversable)
|
||||||
|
|
||||||
|
def test_old_parameter(self):
|
||||||
|
"""
|
||||||
|
Files used to take a 'package' parameter. Make sure anyone
|
||||||
|
passing by name is still supported.
|
||||||
|
"""
|
||||||
|
with suppress_known_deprecation():
|
||||||
|
resources.files(package=self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDiskTests(FilesTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = data01
|
||||||
|
|
||||||
|
|
||||||
|
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenNamespaceTests(FilesTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
from . import namespacedata01
|
||||||
|
|
||||||
|
self.data = namespacedata01
|
||||||
|
|
||||||
|
|
||||||
|
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
|
class SiteDir:
|
||||||
|
def setUp(self):
|
||||||
|
self.fixtures = contextlib.ExitStack()
|
||||||
|
self.addCleanup(self.fixtures.close)
|
||||||
|
self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
|
self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
|
||||||
|
self.fixtures.enter_context(import_helper.CleanImport())
|
||||||
|
|
||||||
|
|
||||||
|
class ModulesFilesTests(SiteDir, unittest.TestCase):
|
||||||
|
def test_module_resources(self):
|
||||||
|
"""
|
||||||
|
A module can have resources found adjacent to the module.
|
||||||
|
"""
|
||||||
|
spec = {
|
||||||
|
'mod.py': '',
|
||||||
|
'res.txt': 'resources are the best',
|
||||||
|
}
|
||||||
|
_path.build(spec, self.site_dir)
|
||||||
|
import mod
|
||||||
|
|
||||||
|
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
|
||||||
|
assert actual == spec['res.txt']
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
|
||||||
|
def test_implicit_files(self):
|
||||||
|
"""
|
||||||
|
Without any parameter, files() will infer the location as the caller.
|
||||||
|
"""
|
||||||
|
spec = {
|
||||||
|
'somepkg': {
|
||||||
|
'__init__.py': textwrap.dedent(
|
||||||
|
"""
|
||||||
|
import importlib_resources as res
|
||||||
|
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
'res.txt': 'resources are the best',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_path.build(spec, self.site_dir)
|
||||||
|
assert importlib.import_module('somepkg').val == 'resources are the best'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
89
libs/common/importlib_resources/tests/test_open.py
Normal file
89
libs/common/importlib_resources/tests/test_open.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import importlib_resources as resources
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
class CommonBinaryTests(util.CommonTests, unittest.TestCase):
|
||||||
|
def execute(self, package, path):
|
||||||
|
target = resources.files(package).joinpath(path)
|
||||||
|
with target.open('rb'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTextTests(util.CommonTests, unittest.TestCase):
|
||||||
|
def execute(self, package, path):
|
||||||
|
target = resources.files(package).joinpath(path)
|
||||||
|
with target.open(encoding='utf-8'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenTests:
|
||||||
|
def test_open_binary(self):
|
||||||
|
target = resources.files(self.data) / 'binary.file'
|
||||||
|
with target.open('rb') as fp:
|
||||||
|
result = fp.read()
|
||||||
|
self.assertEqual(result, bytes(range(4)))
|
||||||
|
|
||||||
|
def test_open_text_default_encoding(self):
|
||||||
|
target = resources.files(self.data) / 'utf-8.file'
|
||||||
|
with target.open(encoding='utf-8') as fp:
|
||||||
|
result = fp.read()
|
||||||
|
self.assertEqual(result, 'Hello, UTF-8 world!\n')
|
||||||
|
|
||||||
|
def test_open_text_given_encoding(self):
|
||||||
|
target = resources.files(self.data) / 'utf-16.file'
|
||||||
|
with target.open(encoding='utf-16', errors='strict') as fp:
|
||||||
|
result = fp.read()
|
||||||
|
self.assertEqual(result, 'Hello, UTF-16 world!\n')
|
||||||
|
|
||||||
|
def test_open_text_with_errors(self):
|
||||||
|
"""
|
||||||
|
Raises UnicodeError without the 'errors' argument.
|
||||||
|
"""
|
||||||
|
target = resources.files(self.data) / 'utf-16.file'
|
||||||
|
with target.open(encoding='utf-8', errors='strict') as fp:
|
||||||
|
self.assertRaises(UnicodeError, fp.read)
|
||||||
|
with target.open(encoding='utf-8', errors='ignore') as fp:
|
||||||
|
result = fp.read()
|
||||||
|
self.assertEqual(
|
||||||
|
result,
|
||||||
|
'H\x00e\x00l\x00l\x00o\x00,\x00 '
|
||||||
|
'\x00U\x00T\x00F\x00-\x001\x006\x00 '
|
||||||
|
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_open_binary_FileNotFoundError(self):
|
||||||
|
target = resources.files(self.data) / 'does-not-exist'
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
target.open('rb')
|
||||||
|
|
||||||
|
def test_open_text_FileNotFoundError(self):
|
||||||
|
target = resources.files(self.data) / 'does-not-exist'
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
target.open(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDiskTests(OpenTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = data01
|
||||||
|
|
||||||
|
|
||||||
|
class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
from . import namespacedata01
|
||||||
|
|
||||||
|
self.data = namespacedata01
|
||||||
|
|
||||||
|
|
||||||
|
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
65
libs/common/importlib_resources/tests/test_path.py
Normal file
65
libs/common/importlib_resources/tests/test_path.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import io
|
||||||
|
import pathlib
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import importlib_resources as resources
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTests(util.CommonTests, unittest.TestCase):
|
||||||
|
def execute(self, package, path):
|
||||||
|
with resources.as_file(resources.files(package).joinpath(path)):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PathTests:
|
||||||
|
def test_reading(self):
|
||||||
|
"""
|
||||||
|
Path should be readable and a pathlib.Path instance.
|
||||||
|
"""
|
||||||
|
target = resources.files(self.data) / 'utf-8.file'
|
||||||
|
with resources.as_file(target) as path:
|
||||||
|
self.assertIsInstance(path, pathlib.Path)
|
||||||
|
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
|
||||||
|
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
class PathDiskTests(PathTests, unittest.TestCase):
|
||||||
|
data = data01
|
||||||
|
|
||||||
|
def test_natural_path(self):
|
||||||
|
"""
|
||||||
|
Guarantee the internal implementation detail that
|
||||||
|
file-system-backed resources do not get the tempdir
|
||||||
|
treatment.
|
||||||
|
"""
|
||||||
|
target = resources.files(self.data) / 'utf-8.file'
|
||||||
|
with resources.as_file(target) as path:
|
||||||
|
assert 'data' in str(path)
|
||||||
|
|
||||||
|
|
||||||
|
class PathMemoryTests(PathTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
file = io.BytesIO(b'Hello, UTF-8 world!\n')
|
||||||
|
self.addCleanup(file.close)
|
||||||
|
self.data = util.create_package(
|
||||||
|
file=file, path=FileNotFoundError("package exists only in memory")
|
||||||
|
)
|
||||||
|
self.data.__spec__.origin = None
|
||||||
|
self.data.__spec__.has_location = False
|
||||||
|
|
||||||
|
|
||||||
|
class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
def test_remove_in_context_manager(self):
|
||||||
|
"""
|
||||||
|
It is not an error if the file that was temporarily stashed on the
|
||||||
|
file system is removed inside the `with` stanza.
|
||||||
|
"""
|
||||||
|
target = resources.files(self.data) / 'utf-8.file'
|
||||||
|
with resources.as_file(target) as path:
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
97
libs/common/importlib_resources/tests/test_read.py
Normal file
97
libs/common/importlib_resources/tests/test_read.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import unittest
|
||||||
|
import importlib_resources as resources
|
||||||
|
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
|
||||||
|
class CommonBinaryTests(util.CommonTests, unittest.TestCase):
|
||||||
|
def execute(self, package, path):
|
||||||
|
resources.files(package).joinpath(path).read_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTextTests(util.CommonTests, unittest.TestCase):
|
||||||
|
def execute(self, package, path):
|
||||||
|
resources.files(package).joinpath(path).read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class ReadTests:
|
||||||
|
def test_read_bytes(self):
|
||||||
|
result = resources.files(self.data).joinpath('binary.file').read_bytes()
|
||||||
|
self.assertEqual(result, bytes(range(4)))
|
||||||
|
|
||||||
|
def test_read_text_default_encoding(self):
|
||||||
|
result = (
|
||||||
|
resources.files(self.data)
|
||||||
|
.joinpath('utf-8.file')
|
||||||
|
.read_text(encoding='utf-8')
|
||||||
|
)
|
||||||
|
self.assertEqual(result, 'Hello, UTF-8 world!\n')
|
||||||
|
|
||||||
|
def test_read_text_given_encoding(self):
|
||||||
|
result = (
|
||||||
|
resources.files(self.data)
|
||||||
|
.joinpath('utf-16.file')
|
||||||
|
.read_text(encoding='utf-16')
|
||||||
|
)
|
||||||
|
self.assertEqual(result, 'Hello, UTF-16 world!\n')
|
||||||
|
|
||||||
|
def test_read_text_with_errors(self):
|
||||||
|
"""
|
||||||
|
Raises UnicodeError without the 'errors' argument.
|
||||||
|
"""
|
||||||
|
target = resources.files(self.data) / 'utf-16.file'
|
||||||
|
self.assertRaises(UnicodeError, target.read_text, encoding='utf-8')
|
||||||
|
result = target.read_text(encoding='utf-8', errors='ignore')
|
||||||
|
self.assertEqual(
|
||||||
|
result,
|
||||||
|
'H\x00e\x00l\x00l\x00o\x00,\x00 '
|
||||||
|
'\x00U\x00T\x00F\x00-\x001\x006\x00 '
|
||||||
|
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReadDiskTests(ReadTests, unittest.TestCase):
|
||||||
|
data = data01
|
||||||
|
|
||||||
|
|
||||||
|
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
def test_read_submodule_resource(self):
|
||||||
|
submodule = import_module('data01.subdirectory')
|
||||||
|
result = resources.files(submodule).joinpath('binary.file').read_bytes()
|
||||||
|
self.assertEqual(result, bytes(range(4, 8)))
|
||||||
|
|
||||||
|
def test_read_submodule_resource_by_name(self):
|
||||||
|
result = (
|
||||||
|
resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
|
||||||
|
)
|
||||||
|
self.assertEqual(result, bytes(range(4, 8)))
|
||||||
|
|
||||||
|
|
||||||
|
class ReadNamespaceTests(ReadTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
from . import namespacedata01
|
||||||
|
|
||||||
|
self.data = namespacedata01
|
||||||
|
|
||||||
|
|
||||||
|
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
def test_read_submodule_resource(self):
|
||||||
|
submodule = import_module('namespacedata01.subdirectory')
|
||||||
|
result = resources.files(submodule).joinpath('binary.file').read_bytes()
|
||||||
|
self.assertEqual(result, bytes(range(12, 16)))
|
||||||
|
|
||||||
|
def test_read_submodule_resource_by_name(self):
|
||||||
|
result = (
|
||||||
|
resources.files('namespacedata01.subdirectory')
|
||||||
|
.joinpath('binary.file')
|
||||||
|
.read_bytes()
|
||||||
|
)
|
||||||
|
self.assertEqual(result, bytes(range(12, 16)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
145
libs/common/importlib_resources/tests/test_reader.py
Normal file
145
libs/common/importlib_resources/tests/test_reader.py
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import pathlib
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from importlib import import_module
|
||||||
|
from importlib_resources.readers import MultiplexedPath, NamespaceReader
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplexedPathTest(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
|
||||||
|
|
||||||
|
def test_init_no_paths(self):
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
MultiplexedPath()
|
||||||
|
|
||||||
|
def test_init_file(self):
|
||||||
|
with self.assertRaises(NotADirectoryError):
|
||||||
|
MultiplexedPath(self.folder / 'binary.file')
|
||||||
|
|
||||||
|
def test_iterdir(self):
|
||||||
|
contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
|
||||||
|
try:
|
||||||
|
contents.remove('__pycache__')
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
self.assertEqual(
|
||||||
|
contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_iterdir_duplicate(self):
|
||||||
|
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
||||||
|
contents = {
|
||||||
|
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
|
||||||
|
}
|
||||||
|
for remove in ('__pycache__', '__init__.pyc'):
|
||||||
|
try:
|
||||||
|
contents.remove(remove)
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
self.assertEqual(
|
||||||
|
contents,
|
||||||
|
{'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_is_dir(self):
|
||||||
|
self.assertEqual(MultiplexedPath(self.folder).is_dir(), True)
|
||||||
|
|
||||||
|
def test_is_file(self):
|
||||||
|
self.assertEqual(MultiplexedPath(self.folder).is_file(), False)
|
||||||
|
|
||||||
|
def test_open_file(self):
|
||||||
|
path = MultiplexedPath(self.folder)
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
path.read_bytes()
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
path.read_text()
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
path.open()
|
||||||
|
|
||||||
|
def test_join_path(self):
|
||||||
|
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
||||||
|
prefix = str(data01.parent)
|
||||||
|
path = MultiplexedPath(self.folder, data01)
|
||||||
|
self.assertEqual(
|
||||||
|
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
||||||
|
os.path.join('namespacedata01', 'binary.file'),
|
||||||
|
)
|
||||||
|
sub = path.joinpath('subdirectory')
|
||||||
|
assert isinstance(sub, MultiplexedPath)
|
||||||
|
assert 'namespacedata01' in str(sub)
|
||||||
|
assert 'data01' in str(sub)
|
||||||
|
self.assertEqual(
|
||||||
|
str(path.joinpath('imaginary'))[len(prefix) + 1 :],
|
||||||
|
os.path.join('namespacedata01', 'imaginary'),
|
||||||
|
)
|
||||||
|
self.assertEqual(path.joinpath(), path)
|
||||||
|
|
||||||
|
def test_join_path_compound(self):
|
||||||
|
path = MultiplexedPath(self.folder)
|
||||||
|
assert not path.joinpath('imaginary/foo.py').exists()
|
||||||
|
|
||||||
|
def test_join_path_common_subdir(self):
|
||||||
|
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
||||||
|
data02 = pathlib.Path(__file__).parent.joinpath('data02')
|
||||||
|
prefix = str(data01.parent)
|
||||||
|
path = MultiplexedPath(data01, data02)
|
||||||
|
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
||||||
|
self.assertEqual(
|
||||||
|
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
||||||
|
os.path.join('data02', 'subdirectory', 'subsubdir'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
self.assertEqual(
|
||||||
|
repr(MultiplexedPath(self.folder)),
|
||||||
|
f"MultiplexedPath('{self.folder}')",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
self.assertEqual(
|
||||||
|
MultiplexedPath(self.folder).name,
|
||||||
|
os.path.basename(self.folder),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NamespaceReaderTest(unittest.TestCase):
|
||||||
|
site_dir = str(pathlib.Path(__file__).parent)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
sys.path.append(cls.site_dir)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
sys.path.remove(cls.site_dir)
|
||||||
|
|
||||||
|
def test_init_error(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
NamespaceReader(['path1', 'path2'])
|
||||||
|
|
||||||
|
def test_resource_path(self):
|
||||||
|
namespacedata01 = import_module('namespacedata01')
|
||||||
|
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
||||||
|
|
||||||
|
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
||||||
|
self.assertEqual(
|
||||||
|
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
reader.resource_path('imaginary'), os.path.join(root, 'imaginary')
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_files(self):
|
||||||
|
namespacedata01 = import_module('namespacedata01')
|
||||||
|
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
||||||
|
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
||||||
|
self.assertIsInstance(reader.files(), MultiplexedPath)
|
||||||
|
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
241
libs/common/importlib_resources/tests/test_resource.py
Normal file
241
libs/common/importlib_resources/tests/test_resource.py
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
import importlib_resources as resources
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from . import data01
|
||||||
|
from . import util
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceTests:
|
||||||
|
# Subclasses are expected to set the `data` attribute.
|
||||||
|
|
||||||
|
def test_is_file_exists(self):
|
||||||
|
target = resources.files(self.data) / 'binary.file'
|
||||||
|
self.assertTrue(target.is_file())
|
||||||
|
|
||||||
|
def test_is_file_missing(self):
|
||||||
|
target = resources.files(self.data) / 'not-a-file'
|
||||||
|
self.assertFalse(target.is_file())
|
||||||
|
|
||||||
|
def test_is_dir(self):
|
||||||
|
target = resources.files(self.data) / 'subdirectory'
|
||||||
|
self.assertFalse(target.is_file())
|
||||||
|
self.assertTrue(target.is_dir())
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceDiskTests(ResourceTests, unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data = data01
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def names(traversable):
|
||||||
|
return {item.name for item in traversable.iterdir()}
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceLoaderTests(unittest.TestCase):
|
||||||
|
def test_resource_contents(self):
|
||||||
|
package = util.create_package(
|
||||||
|
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
||||||
|
)
|
||||||
|
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
||||||
|
|
||||||
|
def test_is_file(self):
|
||||||
|
package = util.create_package(
|
||||||
|
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
||||||
|
)
|
||||||
|
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
||||||
|
|
||||||
|
def test_is_dir(self):
|
||||||
|
package = util.create_package(
|
||||||
|
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
||||||
|
)
|
||||||
|
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
||||||
|
|
||||||
|
def test_resource_missing(self):
|
||||||
|
package = util.create_package(
|
||||||
|
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
||||||
|
)
|
||||||
|
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceCornerCaseTests(unittest.TestCase):
|
||||||
|
def test_package_has_no_reader_fallback(self):
|
||||||
|
"""
|
||||||
|
Test odd ball packages which:
|
||||||
|
# 1. Do not have a ResourceReader as a loader
|
||||||
|
# 2. Are not on the file system
|
||||||
|
# 3. Are not in a zip file
|
||||||
|
"""
|
||||||
|
module = util.create_package(
|
||||||
|
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
||||||
|
)
|
||||||
|
# Give the module a dummy loader.
|
||||||
|
module.__loader__ = object()
|
||||||
|
# Give the module a dummy origin.
|
||||||
|
module.__file__ = '/path/which/shall/not/be/named'
|
||||||
|
module.__spec__.loader = module.__loader__
|
||||||
|
module.__spec__.origin = module.__file__
|
||||||
|
self.assertFalse(resources.files(module).joinpath('A').is_file())
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
|
||||||
|
ZIP_MODULE = 'data01'
|
||||||
|
|
||||||
|
def test_is_submodule_resource(self):
|
||||||
|
submodule = import_module('data01.subdirectory')
|
||||||
|
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
||||||
|
|
||||||
|
def test_read_submodule_resource_by_name(self):
|
||||||
|
self.assertTrue(
|
||||||
|
resources.files('data01.subdirectory').joinpath('binary.file').is_file()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submodule_contents(self):
|
||||||
|
submodule = import_module('data01.subdirectory')
|
||||||
|
self.assertEqual(
|
||||||
|
names(resources.files(submodule)), {'__init__.py', 'binary.file'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submodule_contents_by_name(self):
|
||||||
|
self.assertEqual(
|
||||||
|
names(resources.files('data01.subdirectory')),
|
||||||
|
{'__init__.py', 'binary.file'},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_as_file_directory(self):
|
||||||
|
with resources.as_file(resources.files('data01')) as data:
|
||||||
|
assert data.name == 'data01'
|
||||||
|
assert data.is_dir()
|
||||||
|
assert data.joinpath('subdirectory').is_dir()
|
||||||
|
assert len(list(data.iterdir()))
|
||||||
|
assert not data.parent.exists()
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
|
||||||
|
ZIP_MODULE = 'data02'
|
||||||
|
|
||||||
|
def test_unrelated_contents(self):
|
||||||
|
"""
|
||||||
|
Test thata zip with two unrelated subpackages return
|
||||||
|
distinct resources. Ref python/importlib_resources#44.
|
||||||
|
"""
|
||||||
|
self.assertEqual(
|
||||||
|
names(resources.files('data02.one')),
|
||||||
|
{'__init__.py', 'resource1.txt'},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
names(resources.files('data02.two')),
|
||||||
|
{'__init__.py', 'resource2.txt'},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase):
|
||||||
|
"""Having accessed resources in a zip file should not keep an open
|
||||||
|
reference to the zip.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_iterdir_does_not_keep_open(self):
|
||||||
|
[item.name for item in resources.files('data01').iterdir()]
|
||||||
|
|
||||||
|
def test_is_file_does_not_keep_open(self):
|
||||||
|
resources.files('data01').joinpath('binary.file').is_file()
|
||||||
|
|
||||||
|
def test_is_file_failure_does_not_keep_open(self):
|
||||||
|
resources.files('data01').joinpath('not-present').is_file()
|
||||||
|
|
||||||
|
@unittest.skip("Desired but not supported.")
|
||||||
|
def test_as_file_does_not_keep_open(self): # pragma: no cover
|
||||||
|
resources.as_file(resources.files('data01') / 'binary.file')
|
||||||
|
|
||||||
|
def test_entered_path_does_not_keep_open(self):
|
||||||
|
"""
|
||||||
|
Mimic what certifi does on import to make its bundle
|
||||||
|
available for the process duration.
|
||||||
|
"""
|
||||||
|
resources.as_file(resources.files('data01') / 'binary.file').__enter__()
|
||||||
|
|
||||||
|
def test_read_binary_does_not_keep_open(self):
|
||||||
|
resources.files('data01').joinpath('binary.file').read_bytes()
|
||||||
|
|
||||||
|
def test_read_text_does_not_keep_open(self):
|
||||||
|
resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromNamespaceTests:
|
||||||
|
def test_is_submodule_resource(self):
|
||||||
|
self.assertTrue(
|
||||||
|
resources.files(import_module('namespacedata01'))
|
||||||
|
.joinpath('binary.file')
|
||||||
|
.is_file()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_read_submodule_resource_by_name(self):
|
||||||
|
self.assertTrue(
|
||||||
|
resources.files('namespacedata01').joinpath('binary.file').is_file()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submodule_contents(self):
|
||||||
|
contents = names(resources.files(import_module('namespacedata01')))
|
||||||
|
try:
|
||||||
|
contents.remove('__pycache__')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(
|
||||||
|
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submodule_contents_by_name(self):
|
||||||
|
contents = names(resources.files('namespacedata01'))
|
||||||
|
try:
|
||||||
|
contents.remove('__pycache__')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(
|
||||||
|
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_submodule_sub_contents(self):
|
||||||
|
contents = names(resources.files(import_module('namespacedata01.subdirectory')))
|
||||||
|
try:
|
||||||
|
contents.remove('__pycache__')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(contents, {'binary.file'})
|
||||||
|
|
||||||
|
def test_submodule_sub_contents_by_name(self):
|
||||||
|
contents = names(resources.files('namespacedata01.subdirectory'))
|
||||||
|
try:
|
||||||
|
contents.remove('__pycache__')
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
self.assertEqual(contents, {'binary.file'})
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromNamespaceDiskTests(ResourceFromNamespaceTests, unittest.TestCase):
|
||||||
|
site_dir = str(pathlib.Path(__file__).parent)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
sys.path.append(cls.site_dir)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
sys.path.remove(cls.site_dir)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromNamespaceZipTests(
|
||||||
|
util.ZipSetupBase,
|
||||||
|
ResourceFromNamespaceTests,
|
||||||
|
unittest.TestCase,
|
||||||
|
):
|
||||||
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
165
libs/common/importlib_resources/tests/util.py
Normal file
165
libs/common/importlib_resources/tests/util.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import abc
|
||||||
|
import importlib
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import pathlib
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from . import data01
|
||||||
|
from ..abc import ResourceReader
|
||||||
|
from ._compat import import_helper, os_helper
|
||||||
|
from . import zip as zip_
|
||||||
|
|
||||||
|
|
||||||
|
from importlib.machinery import ModuleSpec
|
||||||
|
|
||||||
|
|
||||||
|
class Reader(ResourceReader):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
vars(self).update(kwargs)
|
||||||
|
|
||||||
|
def get_resource_reader(self, package):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def open_resource(self, path):
|
||||||
|
self._path = path
|
||||||
|
if isinstance(self.file, Exception):
|
||||||
|
raise self.file
|
||||||
|
return self.file
|
||||||
|
|
||||||
|
def resource_path(self, path_):
|
||||||
|
self._path = path_
|
||||||
|
if isinstance(self.path, Exception):
|
||||||
|
raise self.path
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def is_resource(self, path_):
|
||||||
|
self._path = path_
|
||||||
|
if isinstance(self.path, Exception):
|
||||||
|
raise self.path
|
||||||
|
|
||||||
|
def part(entry):
|
||||||
|
return entry.split('/')
|
||||||
|
|
||||||
|
return any(
|
||||||
|
len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents)
|
||||||
|
)
|
||||||
|
|
||||||
|
def contents(self):
|
||||||
|
if isinstance(self.path, Exception):
|
||||||
|
raise self.path
|
||||||
|
yield from self._contents
|
||||||
|
|
||||||
|
|
||||||
|
def create_package_from_loader(loader, is_package=True):
|
||||||
|
name = 'testingpackage'
|
||||||
|
module = types.ModuleType(name)
|
||||||
|
spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package)
|
||||||
|
module.__spec__ = spec
|
||||||
|
module.__loader__ = loader
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def create_package(file=None, path=None, is_package=True, contents=()):
|
||||||
|
return create_package_from_loader(
|
||||||
|
Reader(file=file, path=path, _contents=contents),
|
||||||
|
is_package,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CommonTests(metaclass=abc.ABCMeta):
|
||||||
|
"""
|
||||||
|
Tests shared by test_open, test_path, and test_read.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def execute(self, package, path):
|
||||||
|
"""
|
||||||
|
Call the pertinent legacy API function (e.g. open_text, path)
|
||||||
|
on package and path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_package_name(self):
|
||||||
|
"""
|
||||||
|
Passing in the package name should succeed.
|
||||||
|
"""
|
||||||
|
self.execute(data01.__name__, 'utf-8.file')
|
||||||
|
|
||||||
|
def test_package_object(self):
|
||||||
|
"""
|
||||||
|
Passing in the package itself should succeed.
|
||||||
|
"""
|
||||||
|
self.execute(data01, 'utf-8.file')
|
||||||
|
|
||||||
|
def test_string_path(self):
|
||||||
|
"""
|
||||||
|
Passing in a string for the path should succeed.
|
||||||
|
"""
|
||||||
|
path = 'utf-8.file'
|
||||||
|
self.execute(data01, path)
|
||||||
|
|
||||||
|
def test_pathlib_path(self):
|
||||||
|
"""
|
||||||
|
Passing in a pathlib.PurePath object for the path should succeed.
|
||||||
|
"""
|
||||||
|
path = pathlib.PurePath('utf-8.file')
|
||||||
|
self.execute(data01, path)
|
||||||
|
|
||||||
|
def test_importing_module_as_side_effect(self):
|
||||||
|
"""
|
||||||
|
The anchor package can already be imported.
|
||||||
|
"""
|
||||||
|
del sys.modules[data01.__name__]
|
||||||
|
self.execute(data01.__name__, 'utf-8.file')
|
||||||
|
|
||||||
|
def test_missing_path(self):
|
||||||
|
"""
|
||||||
|
Attempting to open or read or request the path for a
|
||||||
|
non-existent path should succeed if open_resource
|
||||||
|
can return a viable data stream.
|
||||||
|
"""
|
||||||
|
bytes_data = io.BytesIO(b'Hello, world!')
|
||||||
|
package = create_package(file=bytes_data, path=FileNotFoundError())
|
||||||
|
self.execute(package, 'utf-8.file')
|
||||||
|
self.assertEqual(package.__loader__._path, 'utf-8.file')
|
||||||
|
|
||||||
|
def test_extant_path(self):
|
||||||
|
# Attempting to open or read or request the path when the
|
||||||
|
# path does exist should still succeed. Does not assert
|
||||||
|
# anything about the result.
|
||||||
|
bytes_data = io.BytesIO(b'Hello, world!')
|
||||||
|
# any path that exists
|
||||||
|
path = __file__
|
||||||
|
package = create_package(file=bytes_data, path=path)
|
||||||
|
self.execute(package, 'utf-8.file')
|
||||||
|
self.assertEqual(package.__loader__._path, 'utf-8.file')
|
||||||
|
|
||||||
|
def test_useless_loader(self):
|
||||||
|
package = create_package(file=FileNotFoundError(), path=FileNotFoundError())
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
self.execute(package, 'utf-8.file')
|
||||||
|
|
||||||
|
|
||||||
|
class ZipSetupBase:
|
||||||
|
ZIP_MODULE = 'data01'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.fixtures = contextlib.ExitStack()
|
||||||
|
self.addCleanup(self.fixtures.close)
|
||||||
|
|
||||||
|
modules = import_helper.modules_setup()
|
||||||
|
self.addCleanup(import_helper.modules_cleanup, *modules)
|
||||||
|
|
||||||
|
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
|
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
|
||||||
|
src_path = pathlib.Path(__file__).parent.joinpath(self.ZIP_MODULE)
|
||||||
|
self.fixtures.enter_context(
|
||||||
|
import_helper.DirsOnSysPath(str(zip_.make_zip_file(src_path, modules)))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.data = importlib.import_module(self.ZIP_MODULE)
|
||||||
|
|
||||||
|
|
||||||
|
class ZipSetup(ZipSetupBase):
|
||||||
|
pass
|
32
libs/common/importlib_resources/tests/zip.py
Normal file
32
libs/common/importlib_resources/tests/zip.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"""
|
||||||
|
Generate zip test data files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import zipp
|
||||||
|
|
||||||
|
|
||||||
|
def make_zip_file(src, dst):
|
||||||
|
"""
|
||||||
|
Zip the files in src into a new zipfile at dst.
|
||||||
|
"""
|
||||||
|
with zipfile.ZipFile(dst, 'w') as zf:
|
||||||
|
for src_path, rel in walk(src):
|
||||||
|
dst_name = src.name / pathlib.PurePosixPath(rel.as_posix())
|
||||||
|
zf.write(src_path, dst_name)
|
||||||
|
zipp.CompleteDirs.inject(zf)
|
||||||
|
return dst
|
||||||
|
|
||||||
|
|
||||||
|
def walk(datapath):
|
||||||
|
for dirpath, dirnames, filenames in os.walk(datapath):
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
dirnames.remove('__pycache__')
|
||||||
|
for filename in filenames:
|
||||||
|
res = pathlib.Path(dirpath) / filename
|
||||||
|
rel = res.relative_to(datapath)
|
||||||
|
yield res, rel
|
4
tox.ini
4
tox.ini
|
@ -4,18 +4,18 @@
|
||||||
envlist =
|
envlist =
|
||||||
clean,
|
clean,
|
||||||
check,
|
check,
|
||||||
{py27, py37, py38, py39, py310, py311, py312},
|
{py27, py38, py39, py310, py311, py312, py313},
|
||||||
report
|
report
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython =
|
basepython =
|
||||||
py27: {env:TOXPYTHON:python2.7}
|
py27: {env:TOXPYTHON:python2.7}
|
||||||
py37: {env:TOXPYTHON:python3.7}
|
|
||||||
py38: {env:TOXPYTHON:python3.8}
|
py38: {env:TOXPYTHON:python3.8}
|
||||||
py39: {env:TOXPYTHON:python3.9}
|
py39: {env:TOXPYTHON:python3.9}
|
||||||
py310: {env:TOXPYTHON:python3.10}
|
py310: {env:TOXPYTHON:python3.10}
|
||||||
py311: {env:TOXPYTHON:python3.11}
|
py311: {env:TOXPYTHON:python3.11}
|
||||||
py312: {env:TOXPYTHON:python3.12}
|
py312: {env:TOXPYTHON:python3.12}
|
||||||
|
py313: {env:TOXPYTHON:python3.13}
|
||||||
{clean,check,report,codecov}: {env:TOXPYTHON:python3}
|
{clean,check,report,codecov}: {env:TOXPYTHON:python3}
|
||||||
setenv =
|
setenv =
|
||||||
PYTHONPATH={toxinidir}/tests
|
PYTHONPATH={toxinidir}/tests
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue