mirror of
https://github.com/python/cpython.git
synced 2026-05-06 12:49:07 -04:00
gh-121190: Emit a better error message from importlib.resources.files() when module spec is None" (#148460)
Also merges incidental changes from importlib_resources 7.1. Co-authored by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com>
This commit is contained in:
@@ -7,11 +7,11 @@ import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
import types
|
||||
from typing import cast, Optional, Union
|
||||
from typing import Optional, cast
|
||||
|
||||
from .abc import ResourceReader, Traversable
|
||||
|
||||
Package = Union[types.ModuleType, str]
|
||||
Package = types.ModuleType | str
|
||||
Anchor = Package
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]:
|
||||
# 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[union-attr]
|
||||
reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore[union-attr]
|
||||
if reader is None:
|
||||
return None
|
||||
return reader(spec.name) # type: ignore[union-attr]
|
||||
@@ -50,7 +50,7 @@ def _(cand: str) -> types.ModuleType:
|
||||
|
||||
@resolve.register
|
||||
def _(cand: None) -> types.ModuleType:
|
||||
return resolve(_infer_caller().f_globals["__name__"])
|
||||
return resolve(_infer_caller().f_globals['__name__'])
|
||||
|
||||
|
||||
def _infer_caller():
|
||||
@@ -62,7 +62,7 @@ def _infer_caller():
|
||||
return frame_info.filename == stack[0].filename
|
||||
|
||||
def is_wrapper(frame_info):
|
||||
return frame_info.function == "wrapper"
|
||||
return frame_info.function == 'wrapper'
|
||||
|
||||
stack = inspect.stack()
|
||||
not_this_file = itertools.filterfalse(is_this_file, stack)
|
||||
@@ -71,6 +71,19 @@ def _infer_caller():
|
||||
return next(callers).frame
|
||||
|
||||
|
||||
def _assert_spec(package: types.ModuleType) -> None:
|
||||
"""
|
||||
Provide a nicer error message when package is ``__main__``
|
||||
and its ``__spec__`` is ``None``
|
||||
(https://docs.python.org/3/reference/import.html#main-spec).
|
||||
"""
|
||||
if package.__spec__ is None:
|
||||
raise TypeError(
|
||||
f"Cannot access resources for '{package.__name__}' "
|
||||
"as it does not appear to correspond to an importable module (its __spec__ is None)."
|
||||
)
|
||||
|
||||
|
||||
def from_package(package: types.ModuleType):
|
||||
"""
|
||||
Return a Traversable object for the given package.
|
||||
@@ -79,6 +92,7 @@ def from_package(package: types.ModuleType):
|
||||
# deferred for performance (python/cpython#109829)
|
||||
from ._adapters import wrap_spec
|
||||
|
||||
_assert_spec(package)
|
||||
spec = wrap_spec(package)
|
||||
reader = spec.loader.get_resource_reader(spec.name)
|
||||
return reader.files()
|
||||
@@ -87,7 +101,7 @@ def from_package(package: types.ModuleType):
|
||||
@contextlib.contextmanager
|
||||
def _tempfile(
|
||||
reader,
|
||||
suffix="",
|
||||
suffix='',
|
||||
# gh-93353: Keep a reference to call os.remove() in late Python
|
||||
# finalization.
|
||||
*,
|
||||
|
||||
@@ -2,23 +2,21 @@ import abc
|
||||
import itertools
|
||||
import os
|
||||
import pathlib
|
||||
from collections.abc import Iterable, Iterator
|
||||
from typing import (
|
||||
Any,
|
||||
BinaryIO,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Protocol,
|
||||
Text,
|
||||
TextIO,
|
||||
Union,
|
||||
overload,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
StrPath = str | os.PathLike[str]
|
||||
|
||||
__all__ = ["ResourceReader", "Traversable", "TraversableResources"]
|
||||
|
||||
@@ -151,9 +149,7 @@ class Traversable(Protocol):
|
||||
def open(self, mode: Literal['rb'], *args: Any, **kwargs: Any) -> BinaryIO: ...
|
||||
|
||||
@abc.abstractmethod
|
||||
def open(
|
||||
self, mode: str = 'r', *args: Any, **kwargs: Any
|
||||
) -> Union[TextIO, BinaryIO]:
|
||||
def open(self, mode: str = 'r', *args: Any, **kwargs: Any) -> TextIO | BinaryIO:
|
||||
"""
|
||||
mode may be 'r' or 'rb' to open as text or binary. Return a handle
|
||||
suitable for reading (same as pathlib.Path.open).
|
||||
|
||||
@@ -5,7 +5,7 @@ Interface adapters for low-level readers.
|
||||
import abc
|
||||
import io
|
||||
import itertools
|
||||
from typing import BinaryIO, List
|
||||
from typing import BinaryIO
|
||||
|
||||
from .abc import Traversable, TraversableResources
|
||||
|
||||
@@ -24,14 +24,14 @@ class SimpleReader(abc.ABC):
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def children(self) -> List['SimpleReader']:
|
||||
def children(self) -> list['SimpleReader']:
|
||||
"""
|
||||
Obtain an iterable of SimpleReader for available
|
||||
child containers (e.g. directories).
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def resources(self) -> List[str]:
|
||||
def resources(self) -> list[str]:
|
||||
"""
|
||||
Obtain available named resources for this virtual package.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import functools
|
||||
import pathlib
|
||||
from typing import Dict, Protocol, Union, runtime_checkable
|
||||
from typing import Protocol, Union, runtime_checkable
|
||||
|
||||
####
|
||||
# from jaraco.path 3.7.1
|
||||
@@ -12,7 +12,7 @@ class Symlink(str):
|
||||
"""
|
||||
|
||||
|
||||
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']]
|
||||
FilesSpec = dict[str, Union[str, bytes, Symlink, 'FilesSpec']]
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
@@ -28,13 +28,13 @@ class TreeMaker(Protocol):
|
||||
def symlink_to(self, target): ... # pragma: no cover
|
||||
|
||||
|
||||
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
|
||||
def _ensure_tree_maker(obj: 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]
|
||||
prefix: str | TreeMaker = pathlib.Path(), # type: ignore[assignment]
|
||||
):
|
||||
"""
|
||||
Build a set of files/directories, as described by the spec.
|
||||
@@ -66,7 +66,7 @@ def build(
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def create(content: Union[str, bytes, FilesSpec], path):
|
||||
def create(content: str | bytes | FilesSpec, path):
|
||||
path.mkdir(exist_ok=True)
|
||||
build(content, prefix=path) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@@ -24,51 +24,46 @@ class CompatibilityFilesTests(unittest.TestCase):
|
||||
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'],
|
||||
)
|
||||
assert 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()), [])
|
||||
assert 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()), [])
|
||||
assert list((self.files / 'a' / 'a').iterdir()) == []
|
||||
assert 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())
|
||||
assert not self.files.is_file()
|
||||
assert not self.files.is_dir()
|
||||
|
||||
def test_child_path_is(self):
|
||||
self.assertTrue((self.files / 'a').is_file())
|
||||
self.assertFalse((self.files / 'a').is_dir())
|
||||
assert (self.files / 'a').is_file()
|
||||
assert not (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())
|
||||
assert not (self.files / 'a' / 'a').is_file()
|
||||
assert not (self.files / 'a' / 'a').is_dir()
|
||||
assert not (self.files / 'a' / 'a' / 'a').is_file()
|
||||
assert not (self.files / 'a' / 'a' / 'a').is_dir()
|
||||
|
||||
def test_spec_path_name(self):
|
||||
self.assertEqual(self.files.name, 'testingpackage')
|
||||
assert self.files.name == 'testingpackage'
|
||||
|
||||
def test_child_path_name(self):
|
||||
self.assertEqual((self.files / 'a').name, 'a')
|
||||
assert (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')
|
||||
assert (self.files / 'a' / 'b').name == 'b'
|
||||
assert (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!')
|
||||
assert self.files.read_bytes() == b'Hello, world!'
|
||||
assert 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!'
|
||||
)
|
||||
assert (self.files / 'a').read_bytes() == b'Hello, world!'
|
||||
assert (self.files / 'a').read_text(encoding='utf-8') == 'Hello, world!'
|
||||
|
||||
def test_orphan_path_open(self):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
@@ -86,7 +81,7 @@ class CompatibilityFilesTests(unittest.TestCase):
|
||||
|
||||
def test_wrap_spec(self):
|
||||
spec = wrap_spec(self.package)
|
||||
self.assertIsInstance(spec.loader.get_resource_reader(None), CompatibilityFiles)
|
||||
assert isinstance(spec.loader.get_resource_reader(None), CompatibilityFiles)
|
||||
|
||||
|
||||
class CompatibilityFilesNoReaderTests(unittest.TestCase):
|
||||
@@ -99,4 +94,4 @@ class CompatibilityFilesNoReaderTests(unittest.TestCase):
|
||||
return resources.files(self.package)
|
||||
|
||||
def test_spec_path_joinpath(self):
|
||||
self.assertIsInstance(self.files / 'a', CompatibilityFiles.OrphanPath)
|
||||
assert isinstance(self.files / 'a', CompatibilityFiles.OrphanPath)
|
||||
|
||||
@@ -37,7 +37,7 @@ class FilesTests:
|
||||
def test_joinpath_with_multiple_args(self):
|
||||
files = resources.files(self.data)
|
||||
binfile = files.joinpath('subdirectory', 'binary.file')
|
||||
self.assertTrue(binfile.is_file())
|
||||
assert binfile.is_file()
|
||||
|
||||
|
||||
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
|
||||
|
||||
@@ -38,35 +38,40 @@ class FunctionalAPIBase:
|
||||
with self.subTest(path_parts=path_parts):
|
||||
yield path_parts
|
||||
|
||||
def assertEndsWith(self, string, suffix):
|
||||
"""Assert that `string` ends with `suffix`.
|
||||
@staticmethod
|
||||
def remove_utf16_bom(string):
|
||||
"""Remove an architecture-specific UTF-16 BOM prefix when present.
|
||||
|
||||
Used to ignore an architecture-specific UTF-16 byte-order mark."""
|
||||
self.assertEqual(string[-len(suffix) :], suffix)
|
||||
Some platforms surface UTF-16 BOM bytes as escaped text when the
|
||||
fixture is intentionally decoded as UTF-8 with ``errors='backslashreplace'``.
|
||||
Strip that prefix so assertions validate content consistently."""
|
||||
for bom in ('\\xff\\xfe', '\\xfe\\xff', '\ufeff'):
|
||||
if string.startswith(bom):
|
||||
return string[len(bom) :]
|
||||
return string
|
||||
|
||||
def test_read_text(self):
|
||||
self.assertEqual(
|
||||
resources.read_text(self.anchor01, 'utf-8.file'),
|
||||
'Hello, UTF-8 world!\n',
|
||||
assert (
|
||||
resources.read_text(self.anchor01, 'utf-8.file') == 'Hello, UTF-8 world!\n'
|
||||
)
|
||||
self.assertEqual(
|
||||
assert (
|
||||
resources.read_text(
|
||||
self.anchor02,
|
||||
'subdirectory',
|
||||
'subsubdir',
|
||||
'resource.txt',
|
||||
encoding='utf-8',
|
||||
),
|
||||
'a resource',
|
||||
)
|
||||
== 'a resource'
|
||||
)
|
||||
for path_parts in self._gen_resourcetxt_path_parts():
|
||||
self.assertEqual(
|
||||
assert (
|
||||
resources.read_text(
|
||||
self.anchor02,
|
||||
*path_parts,
|
||||
encoding='utf-8',
|
||||
),
|
||||
'a resource',
|
||||
)
|
||||
== 'a resource'
|
||||
)
|
||||
# Use generic OSError, since e.g. attempting to read a directory can
|
||||
# fail with PermissionError rather than IsADirectoryError
|
||||
@@ -76,46 +81,42 @@ class FunctionalAPIBase:
|
||||
resources.read_text(self.anchor01, 'no-such-file')
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
resources.read_text(self.anchor01, 'utf-16.file')
|
||||
self.assertEqual(
|
||||
assert (
|
||||
resources.read_text(
|
||||
self.anchor01,
|
||||
'binary.file',
|
||||
encoding='latin1',
|
||||
),
|
||||
'\x00\x01\x02\x03',
|
||||
)
|
||||
== '\x00\x01\x02\x03'
|
||||
)
|
||||
self.assertEndsWith( # ignore the BOM
|
||||
assert self.remove_utf16_bom(
|
||||
resources.read_text(
|
||||
self.anchor01,
|
||||
'utf-16.file',
|
||||
errors='backslashreplace',
|
||||
),
|
||||
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
|
||||
errors='backslashreplace',
|
||||
),
|
||||
) == 'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
|
||||
errors='backslashreplace',
|
||||
)
|
||||
|
||||
def test_read_binary(self):
|
||||
self.assertEqual(
|
||||
resources.read_binary(self.anchor01, 'utf-8.file'),
|
||||
b'Hello, UTF-8 world!\n',
|
||||
assert (
|
||||
resources.read_binary(self.anchor01, 'utf-8.file')
|
||||
== b'Hello, UTF-8 world!\n'
|
||||
)
|
||||
for path_parts in self._gen_resourcetxt_path_parts():
|
||||
self.assertEqual(
|
||||
resources.read_binary(self.anchor02, *path_parts),
|
||||
b'a resource',
|
||||
)
|
||||
assert resources.read_binary(self.anchor02, *path_parts) == b'a resource'
|
||||
|
||||
def test_open_text(self):
|
||||
with resources.open_text(self.anchor01, 'utf-8.file') as f:
|
||||
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
|
||||
assert f.read() == 'Hello, UTF-8 world!\n'
|
||||
for path_parts in self._gen_resourcetxt_path_parts():
|
||||
with resources.open_text(
|
||||
self.anchor02,
|
||||
*path_parts,
|
||||
encoding='utf-8',
|
||||
) as f:
|
||||
self.assertEqual(f.read(), 'a resource')
|
||||
assert f.read() == 'a resource'
|
||||
# Use generic OSError, since e.g. attempting to read a directory can
|
||||
# fail with PermissionError rather than IsADirectoryError
|
||||
with self.assertRaises(OSError):
|
||||
@@ -130,53 +131,49 @@ class FunctionalAPIBase:
|
||||
'binary.file',
|
||||
encoding='latin1',
|
||||
) as f:
|
||||
self.assertEqual(f.read(), '\x00\x01\x02\x03')
|
||||
assert f.read() == '\x00\x01\x02\x03'
|
||||
with resources.open_text(
|
||||
self.anchor01,
|
||||
'utf-16.file',
|
||||
errors='backslashreplace',
|
||||
) as f:
|
||||
self.assertEndsWith( # ignore the BOM
|
||||
f.read(),
|
||||
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
|
||||
errors='backslashreplace',
|
||||
),
|
||||
assert self.remove_utf16_bom(f.read()) == 'Hello, UTF-16 world!\n'.encode(
|
||||
'utf-16-le'
|
||||
).decode(
|
||||
errors='backslashreplace',
|
||||
)
|
||||
|
||||
def test_open_binary(self):
|
||||
with resources.open_binary(self.anchor01, 'utf-8.file') as f:
|
||||
self.assertEqual(f.read(), b'Hello, UTF-8 world!\n')
|
||||
assert f.read() == b'Hello, UTF-8 world!\n'
|
||||
for path_parts in self._gen_resourcetxt_path_parts():
|
||||
with resources.open_binary(
|
||||
self.anchor02,
|
||||
*path_parts,
|
||||
) as f:
|
||||
self.assertEqual(f.read(), b'a resource')
|
||||
assert f.read() == b'a resource'
|
||||
|
||||
def test_path(self):
|
||||
with resources.path(self.anchor01, 'utf-8.file') as path:
|
||||
with open(str(path), encoding='utf-8') as f:
|
||||
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
|
||||
assert f.read() == 'Hello, UTF-8 world!\n'
|
||||
with resources.path(self.anchor01) as path:
|
||||
with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f:
|
||||
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
|
||||
assert f.read() == 'Hello, UTF-8 world!\n'
|
||||
|
||||
def test_is_resource(self):
|
||||
is_resource = resources.is_resource
|
||||
self.assertTrue(is_resource(self.anchor01, 'utf-8.file'))
|
||||
self.assertFalse(is_resource(self.anchor01, 'no_such_file'))
|
||||
self.assertFalse(is_resource(self.anchor01))
|
||||
self.assertFalse(is_resource(self.anchor01, 'subdirectory'))
|
||||
assert is_resource(self.anchor01, 'utf-8.file')
|
||||
assert not is_resource(self.anchor01, 'no_such_file')
|
||||
assert not is_resource(self.anchor01)
|
||||
assert not is_resource(self.anchor01, 'subdirectory')
|
||||
for path_parts in self._gen_resourcetxt_path_parts():
|
||||
self.assertTrue(is_resource(self.anchor02, *path_parts))
|
||||
assert is_resource(self.anchor02, *path_parts)
|
||||
|
||||
def test_contents(self):
|
||||
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
|
||||
c = resources.contents(self.anchor01)
|
||||
self.assertGreaterEqual(
|
||||
set(c),
|
||||
{'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'},
|
||||
)
|
||||
assert set(c) >= {'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'}
|
||||
with (
|
||||
self.assertRaises(OSError),
|
||||
warnings_helper.check_warnings((
|
||||
@@ -197,10 +194,7 @@ class FunctionalAPIBase:
|
||||
list(resources.contents(self.anchor01, *path_parts))
|
||||
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
|
||||
c = resources.contents(self.anchor01, 'subdirectory')
|
||||
self.assertGreaterEqual(
|
||||
set(c),
|
||||
{'binary.file'},
|
||||
)
|
||||
assert set(c) >= {'binary.file'}
|
||||
|
||||
@warnings_helper.ignore_warnings(category=DeprecationWarning)
|
||||
def test_common_errors(self):
|
||||
|
||||
@@ -23,19 +23,19 @@ class OpenTests:
|
||||
target = resources.files(self.data) / 'binary.file'
|
||||
with target.open('rb') as fp:
|
||||
result = fp.read()
|
||||
self.assertEqual(result, bytes(range(4)))
|
||||
assert 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')
|
||||
assert 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')
|
||||
assert result == 'Hello, UTF-16 world!\n'
|
||||
|
||||
def test_open_text_with_errors(self):
|
||||
"""
|
||||
@@ -46,11 +46,10 @@ class OpenTests:
|
||||
self.assertRaises(UnicodeError, fp.read)
|
||||
with target.open(encoding='utf-8', errors='ignore') as fp:
|
||||
result = fp.read()
|
||||
self.assertEqual(
|
||||
result,
|
||||
assert result == (
|
||||
'H\x00e\x00l\x00l\x00o\x00,\x00 '
|
||||
'\x00U\x00T\x00F\x00-\x001\x006\x00 '
|
||||
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00',
|
||||
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00'
|
||||
)
|
||||
|
||||
def test_open_binary_FileNotFoundError(self):
|
||||
|
||||
@@ -19,9 +19,9 @@ class PathTests:
|
||||
"""
|
||||
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'))
|
||||
assert isinstance(path, pathlib.Path)
|
||||
assert path.name.endswith("utf-8.file"), repr(path)
|
||||
assert 'Hello, UTF-8 world!\n' == path.read_text(encoding='utf-8')
|
||||
|
||||
|
||||
class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
|
||||
|
||||
@@ -18,23 +18,25 @@ class CommonTextTests(util.CommonTests, unittest.TestCase):
|
||||
class ReadTests:
|
||||
def test_read_bytes(self):
|
||||
result = resources.files(self.data).joinpath('binary.file').read_bytes()
|
||||
self.assertEqual(result, bytes(range(4)))
|
||||
assert result == bytes(range(4))
|
||||
|
||||
def test_read_text_default_encoding(self):
|
||||
result = (
|
||||
resources.files(self.data)
|
||||
resources
|
||||
.files(self.data)
|
||||
.joinpath('utf-8.file')
|
||||
.read_text(encoding='utf-8')
|
||||
)
|
||||
self.assertEqual(result, 'Hello, UTF-8 world!\n')
|
||||
assert result == 'Hello, UTF-8 world!\n'
|
||||
|
||||
def test_read_text_given_encoding(self):
|
||||
result = (
|
||||
resources.files(self.data)
|
||||
resources
|
||||
.files(self.data)
|
||||
.joinpath('utf-16.file')
|
||||
.read_text(encoding='utf-16')
|
||||
)
|
||||
self.assertEqual(result, 'Hello, UTF-16 world!\n')
|
||||
assert result == 'Hello, UTF-16 world!\n'
|
||||
|
||||
def test_read_text_with_errors(self):
|
||||
"""
|
||||
@@ -43,11 +45,10 @@ class ReadTests:
|
||||
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,
|
||||
assert result == (
|
||||
'H\x00e\x00l\x00l\x00o\x00,\x00 '
|
||||
'\x00U\x00T\x00F\x00-\x001\x006\x00 '
|
||||
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00',
|
||||
'\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00'
|
||||
)
|
||||
|
||||
|
||||
@@ -59,13 +60,13 @@ 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)))
|
||||
assert 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)))
|
||||
assert result == bytes(range(4, 8))
|
||||
|
||||
|
||||
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
|
||||
@@ -78,15 +79,16 @@ class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
|
||||
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)))
|
||||
assert result == bytes(range(12, 16))
|
||||
|
||||
def test_read_submodule_resource_by_name(self):
|
||||
result = (
|
||||
resources.files('namespacedata01.subdirectory')
|
||||
resources
|
||||
.files('namespacedata01.subdirectory')
|
||||
.joinpath('binary.file')
|
||||
.read_bytes()
|
||||
)
|
||||
self.assertEqual(result, bytes(range(12, 16)))
|
||||
assert result == bytes(range(12, 16))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -30,9 +30,7 @@ class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||
contents.remove('__pycache__')
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
self.assertEqual(
|
||||
contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
|
||||
)
|
||||
assert contents == {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
|
||||
|
||||
def test_iterdir_duplicate(self):
|
||||
contents = {
|
||||
@@ -43,16 +41,19 @@ class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||
contents.remove(remove)
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
self.assertEqual(
|
||||
contents,
|
||||
{'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'},
|
||||
)
|
||||
assert 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)
|
||||
assert MultiplexedPath(self.folder).is_dir()
|
||||
|
||||
def test_is_file(self):
|
||||
self.assertEqual(MultiplexedPath(self.folder).is_file(), False)
|
||||
assert not MultiplexedPath(self.folder).is_file()
|
||||
|
||||
def test_open_file(self):
|
||||
path = MultiplexedPath(self.folder)
|
||||
@@ -66,19 +67,17 @@ class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||
def test_join_path(self):
|
||||
prefix = str(self.folder.parent)
|
||||
path = MultiplexedPath(self.folder, self.data01)
|
||||
self.assertEqual(
|
||||
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
|
||||
os.path.join('namespacedata01', 'binary.file'),
|
||||
assert 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'),
|
||||
assert str(path.joinpath('imaginary'))[len(prefix) + 1 :] == os.path.join(
|
||||
'namespacedata01', 'imaginary'
|
||||
)
|
||||
self.assertEqual(path.joinpath(), path)
|
||||
assert path.joinpath() == path
|
||||
|
||||
def test_join_path_compound(self):
|
||||
path = MultiplexedPath(self.folder)
|
||||
@@ -87,23 +86,16 @@ class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
|
||||
def test_join_path_common_subdir(self):
|
||||
prefix = str(self.data02.parent)
|
||||
path = MultiplexedPath(self.data01, self.data02)
|
||||
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
|
||||
self.assertEqual(
|
||||
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
|
||||
os.path.join('data02', 'subdirectory', 'subsubdir'),
|
||||
assert isinstance(path.joinpath('subdirectory'), MultiplexedPath)
|
||||
assert 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}')",
|
||||
)
|
||||
assert repr(MultiplexedPath(self.folder)) == f"MultiplexedPath('{self.folder}')"
|
||||
|
||||
def test_name(self):
|
||||
self.assertEqual(
|
||||
MultiplexedPath(self.folder).name,
|
||||
os.path.basename(self.folder),
|
||||
)
|
||||
assert MultiplexedPath(self.folder).name == os.path.basename(self.folder)
|
||||
|
||||
|
||||
class NamespaceReaderTest(util.DiskSetup, unittest.TestCase):
|
||||
@@ -118,18 +110,14 @@ class NamespaceReaderTest(util.DiskSetup, unittest.TestCase):
|
||||
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
|
||||
|
||||
root = self.data.__path__[0]
|
||||
self.assertEqual(
|
||||
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
|
||||
)
|
||||
self.assertEqual(
|
||||
reader.resource_path('imaginary'), os.path.join(root, 'imaginary')
|
||||
)
|
||||
assert reader.resource_path('binary.file') == os.path.join(root, 'binary.file')
|
||||
assert reader.resource_path('imaginary') == os.path.join(root, 'imaginary')
|
||||
|
||||
def test_files(self):
|
||||
reader = NamespaceReader(self.data.__spec__.submodule_search_locations)
|
||||
root = self.data.__path__[0]
|
||||
self.assertIsInstance(reader.files(), MultiplexedPath)
|
||||
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
|
||||
assert isinstance(reader.files(), MultiplexedPath)
|
||||
assert repr(reader.files()) == f"MultiplexedPath('{root}')"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import importlib.resources as resources
|
||||
import types
|
||||
import unittest
|
||||
from importlib import import_module
|
||||
|
||||
@@ -10,16 +11,16 @@ class ResourceTests:
|
||||
|
||||
def test_is_file_exists(self):
|
||||
target = resources.files(self.data) / 'binary.file'
|
||||
self.assertTrue(target.is_file())
|
||||
assert target.is_file()
|
||||
|
||||
def test_is_file_missing(self):
|
||||
target = resources.files(self.data) / 'not-a-file'
|
||||
self.assertFalse(target.is_file())
|
||||
assert not target.is_file()
|
||||
|
||||
def test_is_dir(self):
|
||||
target = resources.files(self.data) / 'subdirectory'
|
||||
self.assertFalse(target.is_file())
|
||||
self.assertTrue(target.is_dir())
|
||||
assert not target.is_file()
|
||||
assert target.is_dir()
|
||||
|
||||
|
||||
class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase):
|
||||
@@ -39,7 +40,7 @@ class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||
package = util.create_package(
|
||||
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
|
||||
)
|
||||
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
|
||||
assert names(resources.files(package)) == {'A', 'B', 'C'}
|
||||
|
||||
def test_is_file(self):
|
||||
package = util.create_package(
|
||||
@@ -47,7 +48,7 @@ class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||
path=self.data.__file__,
|
||||
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||
)
|
||||
self.assertTrue(resources.files(package).joinpath('B').is_file())
|
||||
assert resources.files(package).joinpath('B').is_file()
|
||||
|
||||
def test_is_dir(self):
|
||||
package = util.create_package(
|
||||
@@ -55,7 +56,7 @@ class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||
path=self.data.__file__,
|
||||
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||
)
|
||||
self.assertTrue(resources.files(package).joinpath('D').is_dir())
|
||||
assert resources.files(package).joinpath('D').is_dir()
|
||||
|
||||
def test_resource_missing(self):
|
||||
package = util.create_package(
|
||||
@@ -63,7 +64,7 @@ class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
|
||||
path=self.data.__file__,
|
||||
contents=['A', 'B', 'C', 'D/E', 'D/F'],
|
||||
)
|
||||
self.assertFalse(resources.files(package).joinpath('Z').is_file())
|
||||
assert not resources.files(package).joinpath('Z').is_file()
|
||||
|
||||
|
||||
class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase):
|
||||
@@ -83,30 +84,26 @@ class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase):
|
||||
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())
|
||||
assert not resources.files(module).joinpath('A').is_file()
|
||||
|
||||
|
||||
class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase):
|
||||
def test_is_submodule_resource(self):
|
||||
submodule = import_module('data01.subdirectory')
|
||||
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
|
||||
assert 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()
|
||||
)
|
||||
assert 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'}
|
||||
)
|
||||
assert 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'},
|
||||
)
|
||||
assert names(resources.files('data01.subdirectory')) == {
|
||||
'__init__.py',
|
||||
'binary.file',
|
||||
}
|
||||
|
||||
def test_as_file_directory(self):
|
||||
with resources.as_file(resources.files('data01')) as data:
|
||||
@@ -125,14 +122,8 @@ class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase):
|
||||
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'},
|
||||
)
|
||||
assert names(resources.files('data02.one')) == {'__init__.py', 'resource1.txt'}
|
||||
assert names(resources.files('data02.two')) == {'__init__.py', 'resource2.txt'}
|
||||
|
||||
|
||||
class DeletingZipsTest(util.ZipSetup, unittest.TestCase):
|
||||
@@ -169,16 +160,15 @@ class DeletingZipsTest(util.ZipSetup, unittest.TestCase):
|
||||
|
||||
class ResourceFromNamespaceTests:
|
||||
def test_is_submodule_resource(self):
|
||||
self.assertTrue(
|
||||
resources.files(import_module('namespacedata01'))
|
||||
assert (
|
||||
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()
|
||||
)
|
||||
assert resources.files('namespacedata01').joinpath('binary.file').is_file()
|
||||
|
||||
def test_submodule_contents(self):
|
||||
contents = names(resources.files(import_module('namespacedata01')))
|
||||
@@ -186,9 +176,7 @@ class ResourceFromNamespaceTests:
|
||||
contents.remove('__pycache__')
|
||||
except KeyError:
|
||||
pass
|
||||
self.assertEqual(
|
||||
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||
)
|
||||
assert contents == {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||
|
||||
def test_submodule_contents_by_name(self):
|
||||
contents = names(resources.files('namespacedata01'))
|
||||
@@ -196,9 +184,7 @@ class ResourceFromNamespaceTests:
|
||||
contents.remove('__pycache__')
|
||||
except KeyError:
|
||||
pass
|
||||
self.assertEqual(
|
||||
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||
)
|
||||
assert contents == {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
|
||||
|
||||
def test_submodule_sub_contents(self):
|
||||
contents = names(resources.files(import_module('namespacedata01.subdirectory')))
|
||||
@@ -206,7 +192,7 @@ class ResourceFromNamespaceTests:
|
||||
contents.remove('__pycache__')
|
||||
except KeyError:
|
||||
pass
|
||||
self.assertEqual(contents, {'binary.file'})
|
||||
assert contents == {'binary.file'}
|
||||
|
||||
def test_submodule_sub_contents_by_name(self):
|
||||
contents = names(resources.files('namespacedata01.subdirectory'))
|
||||
@@ -214,7 +200,7 @@ class ResourceFromNamespaceTests:
|
||||
contents.remove('__pycache__')
|
||||
except KeyError:
|
||||
pass
|
||||
self.assertEqual(contents, {'binary.file'})
|
||||
assert contents == {'binary.file'}
|
||||
|
||||
|
||||
class ResourceFromNamespaceDiskTests(
|
||||
@@ -233,5 +219,24 @@ class ResourceFromNamespaceZipTests(
|
||||
MODULE = 'namespacedata01'
|
||||
|
||||
|
||||
class MainModuleTests(unittest.TestCase):
|
||||
def test_main_module_with_none_spec(self):
|
||||
"""
|
||||
__main__ module with no spec should raise TypeError (for clarity).
|
||||
|
||||
See python/cpython#138531 for details.
|
||||
"""
|
||||
# construct a __main__ module with no __spec__.
|
||||
mainmodule = types.ModuleType("__main__")
|
||||
|
||||
assert mainmodule.__spec__ is None
|
||||
|
||||
with self.assertRaises(
|
||||
TypeError,
|
||||
msg="Cannot access resources for '__main__' as it does not appear to correspond to an importable module (its __spec__ is None).",
|
||||
):
|
||||
resources.files(mainmodule)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -122,7 +122,7 @@ class CommonTestsBase(metaclass=abc.ABCMeta):
|
||||
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')
|
||||
assert package.__loader__._path == 'utf-8.file'
|
||||
|
||||
def test_extant_path(self):
|
||||
# Attempting to open or read or request the path when the
|
||||
@@ -133,7 +133,7 @@ class CommonTestsBase(metaclass=abc.ABCMeta):
|
||||
path = __file__
|
||||
package = create_package(file=bytes_data, path=path)
|
||||
self.execute(package, 'utf-8.file')
|
||||
self.assertEqual(package.__loader__._path, 'utf-8.file')
|
||||
assert package.__loader__._path == 'utf-8.file'
|
||||
|
||||
def test_useless_loader(self):
|
||||
package = create_package(file=FileNotFoundError(), path=FileNotFoundError())
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
``importlib.resources.files()`` now emits a more meaningful error message
|
||||
when module spec is None (as found in some ``__main__`` modules).
|
||||
Reference in New Issue
Block a user