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:
Jason R. Coombs
2026-04-12 18:15:01 -04:00
committed by GitHub
parent b29afe62f7
commit 480edc1aae
14 changed files with 197 additions and 202 deletions
+20 -6
View File
@@ -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.
*,
+3 -7
View File
@@ -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).
+3 -3
View File
@@ -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.
"""
+5 -5
View File
@@ -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):
+15 -13
View File
@@ -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()
+2 -2
View File
@@ -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).