mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-05 20:51:15 -07:00
Bump importlib-resources from 6.4.0 to 6.4.5 (#2394)
* Bump importlib-resources from 6.4.0 to 6.4.5 Bumps [importlib-resources](https://github.com/python/importlib_resources) from 6.4.0 to 6.4.5. - [Release notes](https://github.com/python/importlib_resources/releases) - [Changelog](https://github.com/python/importlib_resources/blob/main/NEWS.rst) - [Commits](https://github.com/python/importlib_resources/compare/v6.4.0...v6.4.5) --- updated-dependencies: - dependency-name: importlib-resources dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Update importlib-resources==6.4.5 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
f3a2c02e96
commit
01589cb8b0
41 changed files with 484 additions and 316 deletions
|
@ -1,4 +1,11 @@
|
||||||
"""Read resources contained within a package."""
|
"""
|
||||||
|
Read resources contained within a package.
|
||||||
|
|
||||||
|
This codebase is shared between importlib.resources in the stdlib
|
||||||
|
and importlib_resources in PyPI. See
|
||||||
|
https://github.com/python/importlib_metadata/wiki/Development-Methodology
|
||||||
|
for more detail.
|
||||||
|
"""
|
||||||
|
|
||||||
from ._common import (
|
from ._common import (
|
||||||
as_file,
|
as_file,
|
||||||
|
@ -7,7 +14,7 @@ from ._common import (
|
||||||
Anchor,
|
Anchor,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .functional import (
|
from ._functional import (
|
||||||
contents,
|
contents,
|
||||||
is_resource,
|
is_resource,
|
||||||
open_binary,
|
open_binary,
|
||||||
|
|
|
@ -66,10 +66,10 @@ def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]:
|
||||||
# zipimport.zipimporter does not support weak references, resulting in a
|
# zipimport.zipimporter does not support weak references, resulting in a
|
||||||
# TypeError. That seems terrible.
|
# TypeError. That seems terrible.
|
||||||
spec = package.__spec__
|
spec = package.__spec__
|
||||||
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore
|
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr]
|
||||||
if reader is None:
|
if reader is None:
|
||||||
return None
|
return None
|
||||||
return reader(spec.name) # type: ignore
|
return reader(spec.name) # type: ignore[union-attr]
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
|
@ -93,12 +93,13 @@ def _infer_caller():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def is_this_file(frame_info):
|
def is_this_file(frame_info):
|
||||||
return frame_info.filename == __file__
|
return frame_info.filename == stack[0].filename
|
||||||
|
|
||||||
def is_wrapper(frame_info):
|
def is_wrapper(frame_info):
|
||||||
return frame_info.function == 'wrapper'
|
return frame_info.function == 'wrapper'
|
||||||
|
|
||||||
not_this_file = itertools.filterfalse(is_this_file, inspect.stack())
|
stack = inspect.stack()
|
||||||
|
not_this_file = itertools.filterfalse(is_this_file, stack)
|
||||||
# also exclude 'wrapper' due to singledispatch in the call stack
|
# also exclude 'wrapper' due to singledispatch in the call stack
|
||||||
callers = itertools.filterfalse(is_wrapper, not_this_file)
|
callers = itertools.filterfalse(is_wrapper, not_this_file)
|
||||||
return next(callers).frame
|
return next(callers).frame
|
||||||
|
@ -182,7 +183,7 @@ def _(path):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _temp_path(dir: tempfile.TemporaryDirectory):
|
def _temp_path(dir: tempfile.TemporaryDirectory):
|
||||||
"""
|
"""
|
||||||
Wrap tempfile.TemporyDirectory to return a pathlib object.
|
Wrap tempfile.TemporaryDirectory to return a pathlib object.
|
||||||
"""
|
"""
|
||||||
with dir as result:
|
with dir as result:
|
||||||
yield pathlib.Path(result)
|
yield pathlib.Path(result)
|
||||||
|
|
|
@ -5,6 +5,6 @@ __all__ = ['ZipPath']
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
from zipfile import Path as ZipPath # type: ignore
|
from zipfile import Path as ZipPath
|
||||||
else:
|
else:
|
||||||
from zipp import Path as ZipPath # type: ignore
|
from zipp import Path as ZipPath
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -5,6 +7,7 @@ import pathlib
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
from . import abc
|
from . import abc
|
||||||
|
|
||||||
|
@ -34,8 +37,10 @@ class FileReader(abc.TraversableResources):
|
||||||
|
|
||||||
class ZipReader(abc.TraversableResources):
|
class ZipReader(abc.TraversableResources):
|
||||||
def __init__(self, loader, module):
|
def __init__(self, loader, module):
|
||||||
_, _, name = module.rpartition('.')
|
self.prefix = loader.prefix.replace('\\', '/')
|
||||||
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
|
if loader.is_package(module):
|
||||||
|
_, _, name = module.rpartition('.')
|
||||||
|
self.prefix += name + '/'
|
||||||
self.archive = loader.archive
|
self.archive = loader.archive
|
||||||
|
|
||||||
def open_resource(self, resource):
|
def open_resource(self, resource):
|
||||||
|
@ -133,27 +138,31 @@ class NamespaceReader(abc.TraversableResources):
|
||||||
def __init__(self, namespace_path):
|
def __init__(self, namespace_path):
|
||||||
if 'NamespacePath' not in str(namespace_path):
|
if 'NamespacePath' not in str(namespace_path):
|
||||||
raise ValueError('Invalid path')
|
raise ValueError('Invalid path')
|
||||||
self.path = MultiplexedPath(*map(self._resolve, namespace_path))
|
self.path = MultiplexedPath(*filter(bool, map(self._resolve, namespace_path)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _resolve(cls, path_str) -> abc.Traversable:
|
def _resolve(cls, path_str) -> abc.Traversable | None:
|
||||||
r"""
|
r"""
|
||||||
Given an item from a namespace path, resolve it to a Traversable.
|
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
|
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
|
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
|
||||||
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
|
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
|
||||||
|
|
||||||
|
path_str might also be a sentinel used by editable packages to
|
||||||
|
trigger other behaviors (see python/importlib_resources#311).
|
||||||
|
In that case, return None.
|
||||||
"""
|
"""
|
||||||
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
|
dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
|
||||||
return dir
|
return next(dirs, None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _candidate_paths(cls, path_str):
|
def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]:
|
||||||
yield pathlib.Path(path_str)
|
yield pathlib.Path(path_str)
|
||||||
yield from cls._resolve_zip_path(path_str)
|
yield from cls._resolve_zip_path(path_str)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_zip_path(path_str):
|
def _resolve_zip_path(path_str: str):
|
||||||
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
|
for match in reversed(list(re.finditer(r'[\\/]', path_str))):
|
||||||
with contextlib.suppress(
|
with contextlib.suppress(
|
||||||
FileNotFoundError,
|
FileNotFoundError,
|
||||||
|
|
|
@ -77,7 +77,7 @@ class ResourceHandle(Traversable):
|
||||||
|
|
||||||
def __init__(self, parent: ResourceContainer, name: str):
|
def __init__(self, parent: ResourceContainer, name: str):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.name = name # type: ignore
|
self.name = name # type: ignore[misc]
|
||||||
|
|
||||||
def is_file(self):
|
def is_file(self):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -2,15 +2,44 @@ import pathlib
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
from typing import runtime_checkable
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
####
|
####
|
||||||
# from jaraco.path 3.4.1
|
# from jaraco.path 3.7.1
|
||||||
|
|
||||||
FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def build(spec: FilesSpec, prefix=pathlib.Path()):
|
class Symlink(str):
|
||||||
|
"""
|
||||||
|
A string indicating the target of a symlink.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']]
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class TreeMaker(Protocol):
|
||||||
|
def __truediv__(self, *args, **kwargs): ... # pragma: no cover
|
||||||
|
|
||||||
|
def mkdir(self, **kwargs): ... # pragma: no cover
|
||||||
|
|
||||||
|
def write_text(self, content, **kwargs): ... # pragma: no cover
|
||||||
|
|
||||||
|
def write_bytes(self, content): ... # pragma: no cover
|
||||||
|
|
||||||
|
def symlink_to(self, target): ... # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
|
||||||
|
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
|
def build(
|
||||||
|
spec: FilesSpec,
|
||||||
|
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore[assignment]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Build a set of files/directories, as described by the spec.
|
Build a set of files/directories, as described by the spec.
|
||||||
|
|
||||||
|
@ -25,21 +54,25 @@ def build(spec: FilesSpec, prefix=pathlib.Path()):
|
||||||
... "__init__.py": "",
|
... "__init__.py": "",
|
||||||
... },
|
... },
|
||||||
... "baz.py": "# Some code",
|
... "baz.py": "# Some code",
|
||||||
... }
|
... "bar.py": Symlink("baz.py"),
|
||||||
|
... },
|
||||||
|
... "bing": Symlink("foo"),
|
||||||
... }
|
... }
|
||||||
>>> target = getfixture('tmp_path')
|
>>> target = getfixture('tmp_path')
|
||||||
>>> build(spec, target)
|
>>> build(spec, target)
|
||||||
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
|
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
|
||||||
'# Some code'
|
'# Some code'
|
||||||
|
>>> target.joinpath('bing/bar.py').read_text(encoding='utf-8')
|
||||||
|
'# Some code'
|
||||||
"""
|
"""
|
||||||
for name, contents in spec.items():
|
for name, contents in spec.items():
|
||||||
create(contents, pathlib.Path(prefix) / name)
|
create(contents, _ensure_tree_maker(prefix) / name)
|
||||||
|
|
||||||
|
|
||||||
@functools.singledispatch
|
@functools.singledispatch
|
||||||
def create(content: Union[str, bytes, FilesSpec], path):
|
def create(content: Union[str, bytes, FilesSpec], path):
|
||||||
path.mkdir(exist_ok=True)
|
path.mkdir(exist_ok=True)
|
||||||
build(content, prefix=path) # type: ignore
|
build(content, prefix=path) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
@create.register
|
@create.register
|
||||||
|
@ -52,5 +85,10 @@ def _(content: str, path):
|
||||||
path.write_text(content, encoding='utf-8')
|
path.write_text(content, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
@create.register
|
||||||
|
def _(content: Symlink, path):
|
||||||
|
path.symlink_to(content)
|
||||||
|
|
||||||
|
|
||||||
# end from jaraco.path
|
# end from jaraco.path
|
||||||
####
|
####
|
||||||
|
|
|
@ -8,3 +8,6 @@ import_helper = try_import('import_helper') or from_test_support(
|
||||||
'modules_setup', 'modules_cleanup', 'DirsOnSysPath'
|
'modules_setup', 'modules_cleanup', 'DirsOnSysPath'
|
||||||
)
|
)
|
||||||
os_helper = try_import('os_helper') or from_test_support('temp_dir')
|
os_helper = try_import('os_helper') or from_test_support('temp_dir')
|
||||||
|
warnings_helper = try_import('warnings_helper') or from_test_support(
|
||||||
|
'ignore_warnings', 'check_warnings'
|
||||||
|
)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
Hello, UTF-8 world!
|
|
|
@ -1 +0,0 @@
|
||||||
one resource
|
|
|
@ -1 +0,0 @@
|
||||||
a resource
|
|
|
@ -1 +0,0 @@
|
||||||
two resource
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
Hello, UTF-8 world!
|
|
|
@ -1,7 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,16 +18,17 @@ class ContentsTests:
|
||||||
assert self.expected <= contents
|
assert self.expected <= contents
|
||||||
|
|
||||||
|
|
||||||
class ContentsDiskTests(ContentsTests, unittest.TestCase):
|
class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
|
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
|
class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase):
|
||||||
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
# no __init__ because of namespace design
|
# no __init__ because of namespace design
|
||||||
'binary.file',
|
'binary.file',
|
||||||
|
@ -36,8 +36,3 @@ class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
|
||||||
'utf-16.file',
|
'utf-16.file',
|
||||||
'utf-8.file',
|
'utf-8.file',
|
||||||
}
|
}
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import py_compile
|
||||||
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -6,11 +10,8 @@ import contextlib
|
||||||
|
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
from ..abc import Traversable
|
from ..abc import Traversable
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
from . import _path
|
from .compat.py39 import os_helper, import_helper
|
||||||
from .compat.py39 import os_helper
|
|
||||||
from .compat.py312 import import_helper
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -48,70 +49,146 @@ class FilesTests:
|
||||||
resources.files(package=self.data)
|
resources.files(package=self.data)
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskTests(FilesTests, unittest.TestCase):
|
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceTests(FilesTests, unittest.TestCase):
|
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
def test_non_paths_in_dunder_path(self):
|
||||||
|
"""
|
||||||
|
Non-path items in a namespace package's ``__path__`` are ignored.
|
||||||
|
|
||||||
|
As reported in python/importlib_resources#311, some tools
|
||||||
|
like Setuptools, when creating editable packages, will inject
|
||||||
|
non-paths into a namespace package's ``__path__``, a
|
||||||
|
sentinel like
|
||||||
|
``__editable__.sample_namespace-1.0.finder.__path_hook__``
|
||||||
|
to cause the ``PathEntryFinder`` to be called when searching
|
||||||
|
for packages. In that case, resources should still be loadable.
|
||||||
|
"""
|
||||||
|
import namespacedata01
|
||||||
|
|
||||||
|
namespacedata01.__path__.append(
|
||||||
|
'__editable__.sample_namespace-1.0.finder.__path_hook__'
|
||||||
|
)
|
||||||
|
|
||||||
|
resources.files(namespacedata01)
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
ZIP_MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
class SiteDir:
|
class DirectSpec:
|
||||||
def setUp(self):
|
"""
|
||||||
self.fixtures = contextlib.ExitStack()
|
Override behavior of ModuleSetup to write a full spec directly.
|
||||||
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))
|
MODULE = 'unused'
|
||||||
self.fixtures.enter_context(import_helper.isolated_modules())
|
|
||||||
|
def load_fixture(self, name):
|
||||||
|
self.tree_on_path(self.spec)
|
||||||
|
|
||||||
|
|
||||||
class ModulesFilesTests(SiteDir, unittest.TestCase):
|
class ModulesFiles:
|
||||||
|
spec = {
|
||||||
|
'mod.py': '',
|
||||||
|
'res.txt': 'resources are the best',
|
||||||
|
}
|
||||||
|
|
||||||
def test_module_resources(self):
|
def test_module_resources(self):
|
||||||
"""
|
"""
|
||||||
A module can have resources found adjacent to the module.
|
A module can have resources found adjacent to the module.
|
||||||
"""
|
"""
|
||||||
spec = {
|
import mod # type: ignore[import-not-found]
|
||||||
'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')
|
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
|
||||||
assert actual == spec['res.txt']
|
assert actual == self.spec['res.txt']
|
||||||
|
|
||||||
|
|
||||||
class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
|
class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase):
|
||||||
def test_implicit_files(self):
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitContextFiles:
|
||||||
|
set_val = textwrap.dedent(
|
||||||
|
f"""
|
||||||
|
import {resources.__name__} as res
|
||||||
|
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
spec = {
|
||||||
|
'somepkg': {
|
||||||
|
'__init__.py': set_val,
|
||||||
|
'submod.py': set_val,
|
||||||
|
'res.txt': 'resources are the best',
|
||||||
|
},
|
||||||
|
'frozenpkg': {
|
||||||
|
'__init__.py': set_val.replace(resources.__name__, 'c_resources'),
|
||||||
|
'res.txt': 'resources are the best',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_implicit_files_package(self):
|
||||||
"""
|
"""
|
||||||
Without any parameter, files() will infer the location as the caller.
|
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'
|
assert importlib.import_module('somepkg').val == 'resources are the best'
|
||||||
|
|
||||||
|
def test_implicit_files_submodule(self):
|
||||||
|
"""
|
||||||
|
Without any parameter, files() will infer the location as the caller.
|
||||||
|
"""
|
||||||
|
assert importlib.import_module('somepkg.submod').val == 'resources are the best'
|
||||||
|
|
||||||
|
def _compile_importlib(self):
|
||||||
|
"""
|
||||||
|
Make a compiled-only copy of the importlib resources package.
|
||||||
|
"""
|
||||||
|
bin_site = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
|
c_resources = pathlib.Path(bin_site, 'c_resources')
|
||||||
|
sources = pathlib.Path(resources.__file__).parent
|
||||||
|
shutil.copytree(sources, c_resources, ignore=lambda *_: ['__pycache__'])
|
||||||
|
|
||||||
|
for dirpath, _, filenames in os.walk(c_resources):
|
||||||
|
for filename in filenames:
|
||||||
|
source_path = pathlib.Path(dirpath) / filename
|
||||||
|
cfile = source_path.with_suffix('.pyc')
|
||||||
|
py_compile.compile(source_path, cfile)
|
||||||
|
pathlib.Path.unlink(source_path)
|
||||||
|
self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site))
|
||||||
|
|
||||||
|
def test_implicit_files_with_compiled_importlib(self):
|
||||||
|
"""
|
||||||
|
Caller detection works for compiled-only resources module.
|
||||||
|
|
||||||
|
python/cpython#123085
|
||||||
|
"""
|
||||||
|
self._compile_importlib()
|
||||||
|
assert importlib.import_module('frozenpkg').val == 'resources are the best'
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitContextFilesDiskTests(
|
||||||
|
DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImplicitContextFilesZipTests(
|
||||||
|
DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1,31 +1,38 @@
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import contextlib
|
import importlib
|
||||||
|
|
||||||
try:
|
from .compat.py39 import warnings_helper
|
||||||
from test.support.warnings_helper import ignore_warnings, check_warnings
|
|
||||||
except ImportError:
|
|
||||||
# older Python versions
|
|
||||||
from test.support import ignore_warnings, check_warnings
|
|
||||||
|
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
# Since the functional API forwards to Traversable, we only test
|
# Since the functional API forwards to Traversable, we only test
|
||||||
# filesystem resources here -- not zip files, namespace packages etc.
|
# filesystem resources here -- not zip files, namespace packages etc.
|
||||||
# We do test for two kinds of Anchor, though.
|
# We do test for two kinds of Anchor, though.
|
||||||
|
|
||||||
|
|
||||||
class StringAnchorMixin:
|
class StringAnchorMixin:
|
||||||
anchor01 = 'importlib_resources.tests.data01'
|
anchor01 = 'data01'
|
||||||
anchor02 = 'importlib_resources.tests.data02'
|
anchor02 = 'data02'
|
||||||
|
|
||||||
|
|
||||||
class ModuleAnchorMixin:
|
class ModuleAnchorMixin:
|
||||||
from . import data01 as anchor01
|
@property
|
||||||
from . import data02 as anchor02
|
def anchor01(self):
|
||||||
|
return importlib.import_module('data01')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def anchor02(self):
|
||||||
|
return importlib.import_module('data02')
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPIBase:
|
class FunctionalAPIBase(util.DiskSetup):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.load_fixture('data02')
|
||||||
|
|
||||||
def _gen_resourcetxt_path_parts(self):
|
def _gen_resourcetxt_path_parts(self):
|
||||||
"""Yield various names of a text file in anchor02, each in a subTest"""
|
"""Yield various names of a text file in anchor02, each in a subTest"""
|
||||||
for path_parts in (
|
for path_parts in (
|
||||||
|
@ -36,6 +43,12 @@ class FunctionalAPIBase:
|
||||||
with self.subTest(path_parts=path_parts):
|
with self.subTest(path_parts=path_parts):
|
||||||
yield path_parts
|
yield path_parts
|
||||||
|
|
||||||
|
def assertEndsWith(self, string, suffix):
|
||||||
|
"""Assert that `string` ends with `suffix`.
|
||||||
|
|
||||||
|
Used to ignore an architecture-specific UTF-16 byte-order mark."""
|
||||||
|
self.assertEqual(string[-len(suffix) :], suffix)
|
||||||
|
|
||||||
def test_read_text(self):
|
def test_read_text(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
resources.read_text(self.anchor01, 'utf-8.file'),
|
resources.read_text(self.anchor01, 'utf-8.file'),
|
||||||
|
@ -76,13 +89,13 @@ class FunctionalAPIBase:
|
||||||
),
|
),
|
||||||
'\x00\x01\x02\x03',
|
'\x00\x01\x02\x03',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEndsWith( # ignore the BOM
|
||||||
resources.read_text(
|
resources.read_text(
|
||||||
self.anchor01,
|
self.anchor01,
|
||||||
'utf-16.file',
|
'utf-16.file',
|
||||||
errors='backslashreplace',
|
errors='backslashreplace',
|
||||||
),
|
),
|
||||||
'Hello, UTF-16 world!\n'.encode('utf-16').decode(
|
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
|
||||||
errors='backslashreplace',
|
errors='backslashreplace',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -128,9 +141,9 @@ class FunctionalAPIBase:
|
||||||
'utf-16.file',
|
'utf-16.file',
|
||||||
errors='backslashreplace',
|
errors='backslashreplace',
|
||||||
) as f:
|
) as f:
|
||||||
self.assertEqual(
|
self.assertEndsWith( # ignore the BOM
|
||||||
f.read(),
|
f.read(),
|
||||||
'Hello, UTF-16 world!\n'.encode('utf-16').decode(
|
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
|
||||||
errors='backslashreplace',
|
errors='backslashreplace',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -163,32 +176,32 @@ class FunctionalAPIBase:
|
||||||
self.assertTrue(is_resource(self.anchor02, *path_parts))
|
self.assertTrue(is_resource(self.anchor02, *path_parts))
|
||||||
|
|
||||||
def test_contents(self):
|
def test_contents(self):
|
||||||
with check_warnings((".*contents.*", DeprecationWarning)):
|
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
|
||||||
c = resources.contents(self.anchor01)
|
c = resources.contents(self.anchor01)
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
set(c),
|
set(c),
|
||||||
{'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'},
|
{'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'},
|
||||||
)
|
)
|
||||||
with contextlib.ExitStack() as cm:
|
with self.assertRaises(OSError), warnings_helper.check_warnings((
|
||||||
cm.enter_context(self.assertRaises(OSError))
|
".*contents.*",
|
||||||
cm.enter_context(check_warnings((".*contents.*", DeprecationWarning)))
|
DeprecationWarning,
|
||||||
|
)):
|
||||||
list(resources.contents(self.anchor01, 'utf-8.file'))
|
list(resources.contents(self.anchor01, 'utf-8.file'))
|
||||||
|
|
||||||
for path_parts in self._gen_resourcetxt_path_parts():
|
for path_parts in self._gen_resourcetxt_path_parts():
|
||||||
with contextlib.ExitStack() as cm:
|
with self.assertRaises(OSError), warnings_helper.check_warnings((
|
||||||
cm.enter_context(self.assertRaises(OSError))
|
".*contents.*",
|
||||||
cm.enter_context(check_warnings((".*contents.*", DeprecationWarning)))
|
DeprecationWarning,
|
||||||
|
)):
|
||||||
list(resources.contents(self.anchor01, *path_parts))
|
list(resources.contents(self.anchor01, *path_parts))
|
||||||
with check_warnings((".*contents.*", DeprecationWarning)):
|
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
|
||||||
c = resources.contents(self.anchor01, 'subdirectory')
|
c = resources.contents(self.anchor01, 'subdirectory')
|
||||||
self.assertGreaterEqual(
|
self.assertGreaterEqual(
|
||||||
set(c),
|
set(c),
|
||||||
{'binary.file'},
|
{'binary.file'},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ignore_warnings(category=DeprecationWarning)
|
@warnings_helper.ignore_warnings(category=DeprecationWarning)
|
||||||
def test_common_errors(self):
|
def test_common_errors(self):
|
||||||
for func in (
|
for func in (
|
||||||
resources.read_text,
|
resources.read_text,
|
||||||
|
@ -227,16 +240,16 @@ class FunctionalAPIBase:
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPITest_StringAnchor(
|
class FunctionalAPITest_StringAnchor(
|
||||||
unittest.TestCase,
|
|
||||||
FunctionalAPIBase,
|
|
||||||
StringAnchorMixin,
|
StringAnchorMixin,
|
||||||
|
FunctionalAPIBase,
|
||||||
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FunctionalAPITest_ModuleAnchor(
|
class FunctionalAPITest_ModuleAnchor(
|
||||||
unittest.TestCase,
|
|
||||||
FunctionalAPIBase,
|
|
||||||
ModuleAnchorMixin,
|
ModuleAnchorMixin,
|
||||||
|
FunctionalAPIBase,
|
||||||
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,16 +64,12 @@ class OpenTests:
|
||||||
target.open(encoding='utf-8')
|
target.open(encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskTests(OpenTests, unittest.TestCase):
|
class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
|
class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
||||||
|
|
||||||
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -82,7 +77,7 @@ class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -3,7 +3,6 @@ import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,9 +24,7 @@ class PathTests:
|
||||||
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
|
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
|
||||||
class PathDiskTests(PathTests, unittest.TestCase):
|
class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
|
||||||
data = data01
|
|
||||||
|
|
||||||
def test_natural_path(self):
|
def test_natural_path(self):
|
||||||
"""
|
"""
|
||||||
Guarantee the internal implementation detail that
|
Guarantee the internal implementation detail that
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
@ -52,8 +51,8 @@ class ReadTests:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReadDiskTests(ReadTests, unittest.TestCase):
|
class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase):
|
||||||
data = data01
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -69,15 +68,12 @@ class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
self.assertEqual(result, bytes(range(4, 8)))
|
self.assertEqual(result, bytes(range(4, 8)))
|
||||||
|
|
||||||
|
|
||||||
class ReadNamespaceTests(ReadTests, unittest.TestCase):
|
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
MODULE = 'namespacedata01'
|
||||||
from . import namespacedata01
|
|
||||||
|
|
||||||
self.data = namespacedata01
|
|
||||||
|
|
||||||
|
|
||||||
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
def test_read_submodule_resource(self):
|
def test_read_submodule_resource(self):
|
||||||
submodule = import_module('namespacedata01.subdirectory')
|
submodule = import_module('namespacedata01.subdirectory')
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import os.path
|
import os.path
|
||||||
import sys
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from importlib_resources.readers import MultiplexedPath, NamespaceReader
|
from importlib_resources.readers import MultiplexedPath, NamespaceReader
|
||||||
|
|
||||||
|
from . import util
|
||||||
|
|
||||||
class MultiplexedPathTest(unittest.TestCase):
|
|
||||||
@classmethod
|
class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||||
def setUpClass(cls):
|
MODULE = 'namespacedata01'
|
||||||
cls.folder = pathlib.Path(__file__).parent / 'namespacedata01'
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.folder = pathlib.Path(self.data.__path__[0])
|
||||||
|
self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent
|
||||||
|
self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent
|
||||||
|
|
||||||
def test_init_no_paths(self):
|
def test_init_no_paths(self):
|
||||||
with self.assertRaises(FileNotFoundError):
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
@ -31,9 +36,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_iterdir_duplicate(self):
|
def test_iterdir_duplicate(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
|
||||||
contents = {
|
contents = {
|
||||||
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
|
path.name for path in MultiplexedPath(self.folder, self.data01).iterdir()
|
||||||
}
|
}
|
||||||
for remove in ('__pycache__', '__init__.pyc'):
|
for remove in ('__pycache__', '__init__.pyc'):
|
||||||
try:
|
try:
|
||||||
|
@ -61,9 +65,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
path.open()
|
path.open()
|
||||||
|
|
||||||
def test_join_path(self):
|
def test_join_path(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
prefix = str(self.folder.parent)
|
||||||
prefix = str(data01.parent)
|
path = MultiplexedPath(self.folder, self.data01)
|
||||||
path = MultiplexedPath(self.folder, data01)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
||||||
os.path.join('namespacedata01', 'binary.file'),
|
os.path.join('namespacedata01', 'binary.file'),
|
||||||
|
@ -83,10 +86,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
assert not path.joinpath('imaginary/foo.py').exists()
|
assert not path.joinpath('imaginary/foo.py').exists()
|
||||||
|
|
||||||
def test_join_path_common_subdir(self):
|
def test_join_path_common_subdir(self):
|
||||||
data01 = pathlib.Path(__file__).parent.joinpath('data01')
|
prefix = str(self.data02.parent)
|
||||||
data02 = pathlib.Path(__file__).parent.joinpath('data02')
|
path = MultiplexedPath(self.data01, self.data02)
|
||||||
prefix = str(data01.parent)
|
|
||||||
path = MultiplexedPath(data01, data02)
|
|
||||||
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
||||||
|
@ -106,16 +107,8 @@ class MultiplexedPathTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NamespaceReaderTest(unittest.TestCase):
|
class NamespaceReaderTest(util.DiskSetup, unittest.TestCase):
|
||||||
site_dir = str(pathlib.Path(__file__).parent)
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
@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):
|
def test_init_error(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
|
@ -125,7 +118,7 @@ class NamespaceReaderTest(unittest.TestCase):
|
||||||
namespacedata01 = import_module('namespacedata01')
|
namespacedata01 = import_module('namespacedata01')
|
||||||
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
||||||
|
|
||||||
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
root = self.data.__path__[0]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
||||||
)
|
)
|
||||||
|
@ -134,9 +127,8 @@ class NamespaceReaderTest(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_files(self):
|
def test_files(self):
|
||||||
namespacedata01 = import_module('namespacedata01')
|
reader = NamespaceReader(self.data.__spec__.submodule_search_locations)
|
||||||
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
root = self.data.__path__[0]
|
||||||
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
|
|
||||||
self.assertIsInstance(reader.files(), MultiplexedPath)
|
self.assertIsInstance(reader.files(), MultiplexedPath)
|
||||||
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import sys
|
|
||||||
import unittest
|
import unittest
|
||||||
import importlib_resources as resources
|
import importlib_resources as resources
|
||||||
import pathlib
|
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from . import util
|
from . import util
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
@ -25,9 +22,8 @@ class ResourceTests:
|
||||||
self.assertTrue(target.is_dir())
|
self.assertTrue(target.is_dir())
|
||||||
|
|
||||||
|
|
||||||
class ResourceDiskTests(ResourceTests, unittest.TestCase):
|
class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase):
|
||||||
def setUp(self):
|
pass
|
||||||
self.data = data01
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
|
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
|
||||||
|
@ -38,33 +34,39 @@ def names(traversable):
|
||||||
return {item.name for item in traversable.iterdir()}
|
return {item.name for item in traversable.iterdir()}
|
||||||
|
|
||||||
|
|
||||||
class ResourceLoaderTests(unittest.TestCase):
|
class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||||
def test_resource_contents(self):
|
def test_resource_contents(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
|
||||||
)
|
)
|
||||||
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
||||||
|
|
||||||
def test_is_file(self):
|
def test_is_file(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
||||||
|
|
||||||
def test_is_dir(self):
|
def test_is_dir(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
||||||
|
|
||||||
def test_resource_missing(self):
|
def test_resource_missing(self):
|
||||||
package = util.create_package(
|
package = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
|
file=self.data,
|
||||||
|
path=self.data.__file__,
|
||||||
|
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||||
)
|
)
|
||||||
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
||||||
|
|
||||||
|
|
||||||
class ResourceCornerCaseTests(unittest.TestCase):
|
class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase):
|
||||||
def test_package_has_no_reader_fallback(self):
|
def test_package_has_no_reader_fallback(self):
|
||||||
"""
|
"""
|
||||||
Test odd ball packages which:
|
Test odd ball packages which:
|
||||||
|
@ -73,7 +75,7 @@ class ResourceCornerCaseTests(unittest.TestCase):
|
||||||
# 3. Are not in a zip file
|
# 3. Are not in a zip file
|
||||||
"""
|
"""
|
||||||
module = util.create_package(
|
module = util.create_package(
|
||||||
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
|
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
|
||||||
)
|
)
|
||||||
# Give the module a dummy loader.
|
# Give the module a dummy loader.
|
||||||
module.__loader__ = object()
|
module.__loader__ = object()
|
||||||
|
@ -84,9 +86,7 @@ class ResourceCornerCaseTests(unittest.TestCase):
|
||||||
self.assertFalse(resources.files(module).joinpath('A').is_file())
|
self.assertFalse(resources.files(module).joinpath('A').is_file())
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
|
class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'data01'
|
|
||||||
|
|
||||||
def test_is_submodule_resource(self):
|
def test_is_submodule_resource(self):
|
||||||
submodule = import_module('data01.subdirectory')
|
submodule = import_module('data01.subdirectory')
|
||||||
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
||||||
|
@ -117,8 +117,8 @@ class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
|
||||||
assert not data.parent.exists()
|
assert not data.parent.exists()
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
|
class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase):
|
||||||
ZIP_MODULE = 'data02'
|
MODULE = 'data02'
|
||||||
|
|
||||||
def test_unrelated_contents(self):
|
def test_unrelated_contents(self):
|
||||||
"""
|
"""
|
||||||
|
@ -135,7 +135,7 @@ class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeletingZipsTest(util.ZipSetupBase, unittest.TestCase):
|
class DeletingZipsTest(util.ZipSetup, unittest.TestCase):
|
||||||
"""Having accessed resources in a zip file should not keep an open
|
"""Having accessed resources in a zip file should not keep an open
|
||||||
reference to the zip.
|
reference to the zip.
|
||||||
"""
|
"""
|
||||||
|
@ -217,24 +217,20 @@ class ResourceFromNamespaceTests:
|
||||||
self.assertEqual(contents, {'binary.file'})
|
self.assertEqual(contents, {'binary.file'})
|
||||||
|
|
||||||
|
|
||||||
class ResourceFromNamespaceDiskTests(ResourceFromNamespaceTests, unittest.TestCase):
|
class ResourceFromNamespaceDiskTests(
|
||||||
site_dir = str(pathlib.Path(__file__).parent)
|
util.DiskSetup,
|
||||||
|
|
||||||
@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,
|
ResourceFromNamespaceTests,
|
||||||
unittest.TestCase,
|
unittest.TestCase,
|
||||||
):
|
):
|
||||||
ZIP_MODULE = 'namespacedata01'
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFromNamespaceZipTests(
|
||||||
|
util.ZipSetup,
|
||||||
|
ResourceFromNamespaceTests,
|
||||||
|
unittest.TestCase,
|
||||||
|
):
|
||||||
|
MODULE = 'namespacedata01'
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -6,10 +6,10 @@ import types
|
||||||
import pathlib
|
import pathlib
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from . import data01
|
|
||||||
from ..abc import ResourceReader
|
from ..abc import ResourceReader
|
||||||
from .compat.py39 import import_helper, os_helper
|
from .compat.py39 import import_helper, os_helper
|
||||||
from . import zip as zip_
|
from . import zip as zip_
|
||||||
|
from . import _path
|
||||||
|
|
||||||
|
|
||||||
from importlib.machinery import ModuleSpec
|
from importlib.machinery import ModuleSpec
|
||||||
|
@ -68,7 +68,7 @@ def create_package(file=None, path=None, is_package=True, contents=()):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommonTests(metaclass=abc.ABCMeta):
|
class CommonTestsBase(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Tests shared by test_open, test_path, and test_read.
|
Tests shared by test_open, test_path, and test_read.
|
||||||
"""
|
"""
|
||||||
|
@ -84,34 +84,34 @@ class CommonTests(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Passing in the package name should succeed.
|
Passing in the package name should succeed.
|
||||||
"""
|
"""
|
||||||
self.execute(data01.__name__, 'utf-8.file')
|
self.execute(self.data.__name__, 'utf-8.file')
|
||||||
|
|
||||||
def test_package_object(self):
|
def test_package_object(self):
|
||||||
"""
|
"""
|
||||||
Passing in the package itself should succeed.
|
Passing in the package itself should succeed.
|
||||||
"""
|
"""
|
||||||
self.execute(data01, 'utf-8.file')
|
self.execute(self.data, 'utf-8.file')
|
||||||
|
|
||||||
def test_string_path(self):
|
def test_string_path(self):
|
||||||
"""
|
"""
|
||||||
Passing in a string for the path should succeed.
|
Passing in a string for the path should succeed.
|
||||||
"""
|
"""
|
||||||
path = 'utf-8.file'
|
path = 'utf-8.file'
|
||||||
self.execute(data01, path)
|
self.execute(self.data, path)
|
||||||
|
|
||||||
def test_pathlib_path(self):
|
def test_pathlib_path(self):
|
||||||
"""
|
"""
|
||||||
Passing in a pathlib.PurePath object for the path should succeed.
|
Passing in a pathlib.PurePath object for the path should succeed.
|
||||||
"""
|
"""
|
||||||
path = pathlib.PurePath('utf-8.file')
|
path = pathlib.PurePath('utf-8.file')
|
||||||
self.execute(data01, path)
|
self.execute(self.data, path)
|
||||||
|
|
||||||
def test_importing_module_as_side_effect(self):
|
def test_importing_module_as_side_effect(self):
|
||||||
"""
|
"""
|
||||||
The anchor package can already be imported.
|
The anchor package can already be imported.
|
||||||
"""
|
"""
|
||||||
del sys.modules[data01.__name__]
|
del sys.modules[self.data.__name__]
|
||||||
self.execute(data01.__name__, 'utf-8.file')
|
self.execute(self.data.__name__, 'utf-8.file')
|
||||||
|
|
||||||
def test_missing_path(self):
|
def test_missing_path(self):
|
||||||
"""
|
"""
|
||||||
|
@ -141,24 +141,66 @@ class CommonTests(metaclass=abc.ABCMeta):
|
||||||
self.execute(package, 'utf-8.file')
|
self.execute(package, 'utf-8.file')
|
||||||
|
|
||||||
|
|
||||||
class ZipSetupBase:
|
fixtures = dict(
|
||||||
ZIP_MODULE = 'data01'
|
data01={
|
||||||
|
'__init__.py': '',
|
||||||
|
'binary.file': bytes(range(4)),
|
||||||
|
'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'),
|
||||||
|
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
|
||||||
|
'subdirectory': {
|
||||||
|
'__init__.py': '',
|
||||||
|
'binary.file': bytes(range(4, 8)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data02={
|
||||||
|
'__init__.py': '',
|
||||||
|
'one': {'__init__.py': '', 'resource1.txt': 'one resource'},
|
||||||
|
'two': {'__init__.py': '', 'resource2.txt': 'two resource'},
|
||||||
|
'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}},
|
||||||
|
},
|
||||||
|
namespacedata01={
|
||||||
|
'binary.file': bytes(range(4)),
|
||||||
|
'utf-16.file': 'Hello, UTF-16 world!\n'.encode('utf-16'),
|
||||||
|
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
|
||||||
|
'subdirectory': {
|
||||||
|
'binary.file': bytes(range(12, 16)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleSetup:
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.fixtures = contextlib.ExitStack()
|
self.fixtures = contextlib.ExitStack()
|
||||||
self.addCleanup(self.fixtures.close)
|
self.addCleanup(self.fixtures.close)
|
||||||
|
|
||||||
self.fixtures.enter_context(import_helper.isolated_modules())
|
self.fixtures.enter_context(import_helper.isolated_modules())
|
||||||
|
self.data = self.load_fixture(self.MODULE)
|
||||||
|
|
||||||
|
def load_fixture(self, module):
|
||||||
|
self.tree_on_path({module: fixtures[module]})
|
||||||
|
return importlib.import_module(module)
|
||||||
|
|
||||||
|
|
||||||
|
class ZipSetup(ModuleSetup):
|
||||||
|
MODULE = 'data01'
|
||||||
|
|
||||||
|
def tree_on_path(self, spec):
|
||||||
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
|
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
|
||||||
src_path = pathlib.Path(__file__).parent.joinpath(self.ZIP_MODULE)
|
|
||||||
self.fixtures.enter_context(
|
self.fixtures.enter_context(
|
||||||
import_helper.DirsOnSysPath(str(zip_.make_zip_file(src_path, modules)))
|
import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules)))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.data = importlib.import_module(self.ZIP_MODULE)
|
|
||||||
|
class DiskSetup(ModuleSetup):
|
||||||
|
MODULE = 'data01'
|
||||||
|
|
||||||
|
def tree_on_path(self, spec):
|
||||||
|
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
|
||||||
|
_path.build(spec, pathlib.Path(temp_dir))
|
||||||
|
self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir))
|
||||||
|
|
||||||
|
|
||||||
class ZipSetup(ZipSetupBase):
|
class CommonTests(DiskSetup, CommonTestsBase):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -2,31 +2,25 @@
|
||||||
Generate zip test data files.
|
Generate zip test data files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
import zipp
|
import zipp
|
||||||
|
|
||||||
|
|
||||||
def make_zip_file(src, dst):
|
def make_zip_file(tree, dst):
|
||||||
"""
|
"""
|
||||||
Zip the files in src into a new zipfile at dst.
|
Zip the files in tree into a new zipfile at dst.
|
||||||
"""
|
"""
|
||||||
with zipfile.ZipFile(dst, 'w') as zf:
|
with zipfile.ZipFile(dst, 'w') as zf:
|
||||||
for src_path, rel in walk(src):
|
for name, contents in walk(tree):
|
||||||
dst_name = src.name / pathlib.PurePosixPath(rel.as_posix())
|
zf.writestr(name, contents)
|
||||||
zf.write(src_path, dst_name)
|
|
||||||
zipp.CompleteDirs.inject(zf)
|
zipp.CompleteDirs.inject(zf)
|
||||||
return dst
|
return dst
|
||||||
|
|
||||||
|
|
||||||
def walk(datapath):
|
def walk(tree, prefix=''):
|
||||||
for dirpath, dirnames, filenames in os.walk(datapath):
|
for name, contents in tree.items():
|
||||||
with contextlib.suppress(ValueError):
|
if isinstance(contents, dict):
|
||||||
dirnames.remove('__pycache__')
|
yield from walk(contents, prefix=f'{prefix}{name}/')
|
||||||
for filename in filenames:
|
else:
|
||||||
res = pathlib.Path(dirpath) / filename
|
yield f'{prefix}{name}', contents
|
||||||
rel = res.relative_to(datapath)
|
|
||||||
yield res, rel
|
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
|
"""
|
||||||
|
A Path-like interface for zipfiles.
|
||||||
|
|
||||||
|
This codebase is shared between zipfile.Path in the stdlib
|
||||||
|
and zipp in PyPI. See
|
||||||
|
https://github.com/python/importlib_metadata/wiki/Development-Methodology
|
||||||
|
for more detail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
import io
|
import io
|
||||||
import posixpath
|
|
||||||
import zipfile
|
|
||||||
import itertools
|
import itertools
|
||||||
import contextlib
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from .compat.py310 import text_encoding
|
from .compat.py310 import text_encoding
|
||||||
from .glob import Translator
|
from .glob import Translator
|
||||||
|
|
||||||
|
from ._functools import save_method_args
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Path']
|
__all__ = ['Path']
|
||||||
|
|
||||||
|
@ -37,7 +48,7 @@ def _parents(path):
|
||||||
def _ancestry(path):
|
def _ancestry(path):
|
||||||
"""
|
"""
|
||||||
Given a path with elements separated by
|
Given a path with elements separated by
|
||||||
posixpath.sep, generate all elements of that path
|
posixpath.sep, generate all elements of that path.
|
||||||
|
|
||||||
>>> list(_ancestry('b/d'))
|
>>> list(_ancestry('b/d'))
|
||||||
['b/d', 'b']
|
['b/d', 'b']
|
||||||
|
@ -49,9 +60,14 @@ def _ancestry(path):
|
||||||
['b']
|
['b']
|
||||||
>>> list(_ancestry(''))
|
>>> list(_ancestry(''))
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
Multiple separators are treated like a single.
|
||||||
|
|
||||||
|
>>> list(_ancestry('//b//d///f//'))
|
||||||
|
['//b//d///f', '//b//d', '//b']
|
||||||
"""
|
"""
|
||||||
path = path.rstrip(posixpath.sep)
|
path = path.rstrip(posixpath.sep)
|
||||||
while path and path != posixpath.sep:
|
while path.rstrip(posixpath.sep):
|
||||||
yield path
|
yield path
|
||||||
path, tail = posixpath.split(path)
|
path, tail = posixpath.split(path)
|
||||||
|
|
||||||
|
@ -73,82 +89,19 @@ class InitializedState:
|
||||||
Mix-in to save the initialization state for pickling.
|
Mix-in to save the initialization state for pickling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@save_method_args
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.__args = args
|
|
||||||
self.__kwargs = kwargs
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return self.__args, self.__kwargs
|
return self._saved___init__.args, self._saved___init__.kwargs
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
args, kwargs = state
|
args, kwargs = state
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SanitizedNames:
|
class CompleteDirs(InitializedState, zipfile.ZipFile):
|
||||||
"""
|
|
||||||
ZipFile mix-in to ensure names are sanitized.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def namelist(self):
|
|
||||||
return list(map(self._sanitize, super().namelist()))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _sanitize(name):
|
|
||||||
r"""
|
|
||||||
Ensure a relative path with posix separators and no dot names.
|
|
||||||
|
|
||||||
Modeled after
|
|
||||||
https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
|
|
||||||
but provides consistent cross-platform behavior.
|
|
||||||
|
|
||||||
>>> san = SanitizedNames._sanitize
|
|
||||||
>>> san('/foo/bar')
|
|
||||||
'foo/bar'
|
|
||||||
>>> san('//foo.txt')
|
|
||||||
'foo.txt'
|
|
||||||
>>> san('foo/.././bar.txt')
|
|
||||||
'foo/bar.txt'
|
|
||||||
>>> san('foo../.bar.txt')
|
|
||||||
'foo../.bar.txt'
|
|
||||||
>>> san('\\foo\\bar.txt')
|
|
||||||
'foo/bar.txt'
|
|
||||||
>>> san('D:\\foo.txt')
|
|
||||||
'D/foo.txt'
|
|
||||||
>>> san('\\\\server\\share\\file.txt')
|
|
||||||
'server/share/file.txt'
|
|
||||||
>>> san('\\\\?\\GLOBALROOT\\Volume3')
|
|
||||||
'?/GLOBALROOT/Volume3'
|
|
||||||
>>> san('\\\\.\\PhysicalDrive1\\root')
|
|
||||||
'PhysicalDrive1/root'
|
|
||||||
|
|
||||||
Retain any trailing slash.
|
|
||||||
>>> san('abc/')
|
|
||||||
'abc/'
|
|
||||||
|
|
||||||
Raises a ValueError if the result is empty.
|
|
||||||
>>> san('../..')
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Empty filename
|
|
||||||
"""
|
|
||||||
|
|
||||||
def allowed(part):
|
|
||||||
return part and part not in {'..', '.'}
|
|
||||||
|
|
||||||
# Remove the drive letter.
|
|
||||||
# Don't use ntpath.splitdrive, because that also strips UNC paths
|
|
||||||
bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
|
|
||||||
clean = bare.replace('\\', '/')
|
|
||||||
parts = clean.split('/')
|
|
||||||
joined = '/'.join(filter(allowed, parts))
|
|
||||||
if not joined:
|
|
||||||
raise ValueError("Empty filename")
|
|
||||||
return joined + '/' * name.endswith('/')
|
|
||||||
|
|
||||||
|
|
||||||
class CompleteDirs(InitializedState, SanitizedNames, zipfile.ZipFile):
|
|
||||||
"""
|
"""
|
||||||
A ZipFile subclass that ensures that implied directories
|
A ZipFile subclass that ensures that implied directories
|
||||||
are always included in the namelist.
|
are always included in the namelist.
|
||||||
|
@ -230,16 +183,18 @@ class FastLookup(CompleteDirs):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def namelist(self):
|
def namelist(self):
|
||||||
with contextlib.suppress(AttributeError):
|
return self._namelist
|
||||||
return self.__names
|
|
||||||
self.__names = super().namelist()
|
@functools.cached_property
|
||||||
return self.__names
|
def _namelist(self):
|
||||||
|
return super().namelist()
|
||||||
|
|
||||||
def _name_set(self):
|
def _name_set(self):
|
||||||
with contextlib.suppress(AttributeError):
|
return self._name_set_prop
|
||||||
return self.__lookup
|
|
||||||
self.__lookup = super()._name_set()
|
@functools.cached_property
|
||||||
return self.__lookup
|
def _name_set_prop(self):
|
||||||
|
return super()._name_set()
|
||||||
|
|
||||||
|
|
||||||
def _extract_text_encoding(encoding=None, *args, **kwargs):
|
def _extract_text_encoding(encoding=None, *args, **kwargs):
|
||||||
|
@ -329,7 +284,7 @@ class Path:
|
||||||
>>> str(path.parent)
|
>>> str(path.parent)
|
||||||
'mem'
|
'mem'
|
||||||
|
|
||||||
If the zipfile has no filename, such attributes are not
|
If the zipfile has no filename, such attributes are not
|
||||||
valid and accessing them will raise an Exception.
|
valid and accessing them will raise an Exception.
|
||||||
|
|
||||||
>>> zf.filename = None
|
>>> zf.filename = None
|
||||||
|
@ -388,7 +343,7 @@ class Path:
|
||||||
if self.is_dir():
|
if self.is_dir():
|
||||||
raise IsADirectoryError(self)
|
raise IsADirectoryError(self)
|
||||||
zip_mode = mode[0]
|
zip_mode = mode[0]
|
||||||
if not self.exists() and zip_mode == 'r':
|
if zip_mode == 'r' and not self.exists():
|
||||||
raise FileNotFoundError(self)
|
raise FileNotFoundError(self)
|
||||||
stream = self.root.open(self.at, zip_mode, pwd=pwd)
|
stream = self.root.open(self.at, zip_mode, pwd=pwd)
|
||||||
if 'b' in mode:
|
if 'b' in mode:
|
||||||
|
@ -470,8 +425,7 @@ class Path:
|
||||||
prefix = re.escape(self.at)
|
prefix = re.escape(self.at)
|
||||||
tr = Translator(seps='/')
|
tr = Translator(seps='/')
|
||||||
matches = re.compile(prefix + tr.translate(pattern)).fullmatch
|
matches = re.compile(prefix + tr.translate(pattern)).fullmatch
|
||||||
names = (data.filename for data in self.root.filelist)
|
return map(self._next, filter(matches, self.root.namelist()))
|
||||||
return map(self._next, filter(matches, names))
|
|
||||||
|
|
||||||
def rglob(self, pattern):
|
def rglob(self, pattern):
|
||||||
return self.glob(f'**/{pattern}')
|
return self.glob(f'**/{pattern}')
|
||||||
|
|
20
lib/zipp/_functools.py
Normal file
20
lib/zipp/_functools.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
# from jaraco.functools 4.0.2
|
||||||
|
def save_method_args(method):
|
||||||
|
"""
|
||||||
|
Wrap a method such that when it is called, the args and kwargs are
|
||||||
|
saved on the method.
|
||||||
|
"""
|
||||||
|
args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs')
|
||||||
|
|
||||||
|
@functools.wraps(method)
|
||||||
|
def wrapper(self, /, *args, **kwargs):
|
||||||
|
attr_name = '_saved_' + method.__name__
|
||||||
|
attr = args_and_kwargs(args, kwargs)
|
||||||
|
setattr(self, attr_name, attr)
|
||||||
|
return method(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
37
lib/zipp/compat/overlay.py
Normal file
37
lib/zipp/compat/overlay.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""
|
||||||
|
Expose zipp.Path as .zipfile.Path.
|
||||||
|
|
||||||
|
Includes everything else in ``zipfile`` to match future usage. Just
|
||||||
|
use:
|
||||||
|
|
||||||
|
>>> from zipp.compat.overlay import zipfile
|
||||||
|
|
||||||
|
in place of ``import zipfile``.
|
||||||
|
|
||||||
|
Relative imports are supported too.
|
||||||
|
|
||||||
|
>>> from zipp.compat.overlay.zipfile import ZipInfo
|
||||||
|
|
||||||
|
The ``zipfile`` object added to ``sys.modules`` needs to be
|
||||||
|
hashable (#126).
|
||||||
|
|
||||||
|
>>> _ = hash(sys.modules['zipp.compat.overlay.zipfile'])
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
import zipp
|
||||||
|
|
||||||
|
|
||||||
|
class HashableNamespace(types.SimpleNamespace):
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(tuple(vars(self)))
|
||||||
|
|
||||||
|
|
||||||
|
zipfile = HashableNamespace(**vars(importlib.import_module('zipfile')))
|
||||||
|
zipfile.Path = zipp.Path
|
||||||
|
zipfile._path = zipp
|
||||||
|
|
||||||
|
sys.modules[__name__ + '.zipfile'] = zipfile # type: ignore[assignment]
|
|
@ -1,5 +1,5 @@
|
||||||
import sys
|
|
||||||
import io
|
import io
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
||||||
|
@ -7,5 +7,7 @@ def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
text_encoding = (
|
text_encoding = (
|
||||||
io.text_encoding if sys.version_info > (3, 10) else _text_encoding # type: ignore
|
io.text_encoding # type: ignore[unused-ignore, attr-defined]
|
||||||
|
if sys.version_info > (3, 10)
|
||||||
|
else _text_encoding
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
_default_seps = os.sep + str(os.altsep) * bool(os.altsep)
|
_default_seps = os.sep + str(os.altsep) * bool(os.altsep)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ class Translator:
|
||||||
"""
|
"""
|
||||||
Given a glob pattern, produce a regex that matches it.
|
Given a glob pattern, produce a regex that matches it.
|
||||||
"""
|
"""
|
||||||
return self.extend(self.translate_core(pattern))
|
return self.extend(self.match_dirs(self.translate_core(pattern)))
|
||||||
|
|
||||||
def extend(self, pattern):
|
def extend(self, pattern):
|
||||||
r"""
|
r"""
|
||||||
|
@ -41,6 +40,14 @@ class Translator:
|
||||||
"""
|
"""
|
||||||
return rf'(?s:{pattern})\Z'
|
return rf'(?s:{pattern})\Z'
|
||||||
|
|
||||||
|
def match_dirs(self, pattern):
|
||||||
|
"""
|
||||||
|
Ensure that zipfile.Path directory names are matched.
|
||||||
|
|
||||||
|
zipfile.Path directory names always end in a slash.
|
||||||
|
"""
|
||||||
|
return rf'{pattern}[/]?'
|
||||||
|
|
||||||
def translate_core(self, pattern):
|
def translate_core(self, pattern):
|
||||||
r"""
|
r"""
|
||||||
Given a glob pattern, produce a regex that matches it.
|
Given a glob pattern, produce a regex that matches it.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
apscheduler==3.10.1
|
apscheduler==3.10.1
|
||||||
cryptography==43.0.0
|
cryptography==43.0.0
|
||||||
importlib-metadata==8.2.0
|
importlib-metadata==8.2.0
|
||||||
importlib-resources==6.4.0
|
importlib-resources==6.4.5
|
||||||
pyinstaller==6.8.0
|
pyinstaller==6.8.0
|
||||||
pyopenssl==24.2.1
|
pyopenssl==24.2.1
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ html5lib==1.1
|
||||||
httpagentparser==1.9.5
|
httpagentparser==1.9.5
|
||||||
idna==3.7
|
idna==3.7
|
||||||
importlib-metadata==8.2.0
|
importlib-metadata==8.2.0
|
||||||
importlib-resources==6.4.0
|
importlib-resources==6.4.5
|
||||||
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
|
git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois
|
||||||
IPy==1.01
|
IPy==1.01
|
||||||
Mako==1.3.5
|
Mako==1.3.5
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue