diff --git a/Lib/importlib/resources/_adapters.py b/Lib/importlib/resources/_adapters.py index 50688fbb666658c..e2ad6879be67441 100644 --- a/Lib/importlib/resources/_adapters.py +++ b/Lib/importlib/resources/_adapters.py @@ -66,10 +66,14 @@ def is_file(self): is_dir = is_file - def joinpath(self, other): + def joinpath(self, *descendants): + if not descendants: + return self if not self._reader: - return CompatibilityFiles.OrphanPath(other) - return CompatibilityFiles.ChildPath(self._reader, other) + return CompatibilityFiles.OrphanPath(*descendants) + first, *rest = descendants + child = CompatibilityFiles.ChildPath(self._reader, first) + return child.joinpath(*rest) @property def name(self): @@ -97,8 +101,10 @@ def is_file(self): def is_dir(self): return not self.is_file() - def joinpath(self, other): - return CompatibilityFiles.OrphanPath(self.name, other) + def joinpath(self, *descendants): + if not descendants: + return self + return CompatibilityFiles.OrphanPath(self.name, *descendants) @property def name(self): @@ -128,8 +134,10 @@ def is_file(self): is_dir = is_file - def joinpath(self, other): - return CompatibilityFiles.OrphanPath(*self._path, other) + def joinpath(self, *descendants): + if not descendants: + return self + return CompatibilityFiles.OrphanPath(*self._path, *descendants) @property def name(self): diff --git a/Lib/test/test_importlib/resources/test_compatibilty_files.py b/Lib/test/test_importlib/resources/test_compatibilty_files.py index 2fd39bedf258d1a..e70e3ad37186017 100644 --- a/Lib/test/test_importlib/resources/test_compatibilty_files.py +++ b/Lib/test/test_importlib/resources/test_compatibilty_files.py @@ -6,6 +6,8 @@ wrap_spec, ) +from test.support import warnings_helper + from . import util @@ -79,6 +81,37 @@ def test_orphan_path_invalid(self): with self.assertRaises(ValueError): CompatibilityFiles.OrphanPath() + def test_spec_path_joinpath_no_descendants(self): + files = self.files + assert files.joinpath() is files + + def test_child_path_joinpath_no_descendants(self): + child = self.files / 'a' + assert child.joinpath() is child + + def test_orphan_path_joinpath_no_descendants(self): + orphan = self.files / 'a' / 'b' + assert orphan.joinpath() is orphan + + def test_joinpath_multiple_descendants(self): + # Several descendants at once give the same result as applying them + # one at a time, as documented for Traversable.joinpath(). + files = self.files + for path, expected in ( + (files.joinpath('a', 'b'), files / 'a' / 'b'), + ((files / 'a').joinpath('b', 'c'), files / 'a' / 'b' / 'c'), + ((files / 'a' / 'b').joinpath('c', 'd'), files / 'a' / 'b' / 'c' / 'd'), + ): + assert isinstance(path, CompatibilityFiles.OrphanPath) + assert path._path == expected._path + + def test_functional_api_without_path_names(self): + # gh-127337: these call joinpath() with no descendants at all. + package = self.package + with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)): + assert sorted(resources.contents(package)) == ['a', 'b', 'c'] + assert not resources.is_resource(package) + def test_wrap_spec(self): spec = wrap_spec(self.package) assert isinstance(spec.loader.get_resource_reader(None), CompatibilityFiles) @@ -95,3 +128,14 @@ def files(self): def test_spec_path_joinpath(self): assert isinstance(self.files / 'a', CompatibilityFiles.OrphanPath) + + def test_spec_path_joinpath_no_descendants(self): + # Returns self rather than an OrphanPath with no parts, which would + # be rejected by OrphanPath.__init__(). + files = self.files + assert files.joinpath() is files + + def test_spec_path_joinpath_multiple_descendants(self): + path = self.files.joinpath('a', 'b') + assert isinstance(path, CompatibilityFiles.OrphanPath) + assert path._path == (self.files / 'a' / 'b')._path diff --git a/Misc/NEWS.d/next/Library/2026-07-30-13-36-06.gh-issue-127337.ahcvtH.rst b/Misc/NEWS.d/next/Library/2026-07-30-13-36-06.gh-issue-127337.ahcvtH.rst new file mode 100644 index 000000000000000..0883c8f497fafe4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-30-13-36-06.gh-issue-127337.ahcvtH.rst @@ -0,0 +1,9 @@ +Fix a regression in :mod:`importlib.resources` where functions such as +:func:`~importlib.resources.is_resource` and +:func:`~importlib.resources.contents` raised :exc:`TypeError` for a package +whose loader provides only a legacy +:class:`~importlib.resources.abc.ResourceReader`. The internal +``CompatibilityFiles`` traversables wrapping such a reader now accept the +zero-or-more argument ``joinpath()`` signature specified by +:meth:`importlib.resources.abc.Traversable.joinpath`. +Patch by pikammmmm.