diff --git a/Lib/importlib/resources/simple.py b/Lib/importlib/resources/simple.py index 2e75299b13aabf..12a540d5ff253f 100644 --- a/Lib/importlib/resources/simple.py +++ b/Lib/importlib/resources/simple.py @@ -55,6 +55,10 @@ class ResourceContainer(Traversable): def __init__(self, reader: SimpleReader): self.reader = reader + @property + def name(self): + return self.reader.name + def is_dir(self): return True @@ -62,7 +66,7 @@ def is_file(self): return False def iterdir(self): - files = (ResourceHandle(self, name) for name in self.reader.resources) + files = (ResourceHandle(self, name) for name in self.reader.resources()) dirs = map(ResourceContainer, self.reader.children()) return itertools.chain(files, dirs) @@ -77,7 +81,11 @@ class ResourceHandle(Traversable): def __init__(self, parent: ResourceContainer, name: str): self.parent = parent - self.name = name # type: ignore[misc] + self._name = name + + @property + def name(self): + return self._name def is_file(self): return True @@ -85,13 +93,18 @@ def is_file(self): def is_dir(self): return False + def iterdir(self): + return iter([]) + def open(self, mode='r', *args, **kwargs): stream = self.parent.reader.open_binary(self.name) if 'b' not in mode: stream = io.TextIOWrapper(stream, *args, **kwargs) return stream - def joinpath(self, name): + def joinpath(self, *descendants): + if not descendants: + return self raise RuntimeError("Cannot traverse into a resource") diff --git a/Lib/test/test_importlib/resources/test_reader.py b/Lib/test/test_importlib/resources/test_reader.py index ed5693ab416798..7fd9cd2212ccd7 100644 --- a/Lib/test/test_importlib/resources/test_reader.py +++ b/Lib/test/test_importlib/resources/test_reader.py @@ -1,9 +1,11 @@ +import io import os.path import pathlib import unittest from importlib import import_module from importlib.readers import MultiplexedPath, NamespaceReader +from importlib.resources.simple import ResourceContainer, ResourceHandle, SimpleReader from . import util @@ -133,5 +135,67 @@ def test_files(self): self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") +class SimpleReaderTest(unittest.TestCase): + def test_resource_container_instantiation(self): + class R(SimpleReader): + @property + def package(self): + return 'x' + + def children(self): + return [] + + def resources(self): + return [] + + def open_binary(self, r): + return io.BytesIO(b'') + + container = ResourceContainer(R()) + self.assertEqual(container.name, 'x') + self.assertTrue(container.is_dir()) + + def test_resource_container_iterdir(self): + class R(SimpleReader): + @property + def package(self): + return 'test' + + def children(self): + return [] + + def resources(self): + return ['file.txt'] + + def open_binary(self, r): + return io.BytesIO(b'data') + + container = ResourceContainer(R()) + items = list(container.iterdir()) + self.assertEqual(len(items), 1) + self.assertIsInstance(items[0], ResourceHandle) + + def test_resource_handle_joinpath(self): + class R(SimpleReader): + @property + def package(self): + return 'test' + + def children(self): + return [] + + def resources(self): + return [] + + def open_binary(self, r): + return io.BytesIO(b'') + + container = ResourceContainer(R()) + handle = ResourceHandle(container, 'file.txt') + self.assertIs(handle.joinpath(), handle) + with self.assertRaises(RuntimeError): + handle.joinpath('subpath') + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-02-26-15-46-50.gh-issue-145260.wmT5hp.rst b/Misc/NEWS.d/next/Library/2026-02-26-15-46-50.gh-issue-145260.wmT5hp.rst new file mode 100644 index 00000000000000..3675cfe9af23ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-26-15-46-50.gh-issue-145260.wmT5hp.rst @@ -0,0 +1,5 @@ +Fix ``ResourceContainer`` and ``ResourceHandle`` in +``importlib.resources.simple`` being uninstantiable due to missing ``name`` +property required by :class:`~importlib.resources.abc.Traversable`. Also fix +``ResourceContainer.iterdir()`` not calling ``resources()`` and +``ResourceHandle.joinpath`` signature mismatch with base class.