Skip to content

gh-127337: Fix importlib.resources for legacy ResourceReader loaders - #154919

Open
pikammmmm wants to merge 1 commit into
python:mainfrom
pikammmmm:gh-127337-compatibilityfiles-joinpath
Open

gh-127337: Fix importlib.resources for legacy ResourceReader loaders#154919
pikammmmm wants to merge 1 commit into
python:mainfrom
pikammmmm:gh-127337-compatibilityfiles-joinpath

Conversation

@pikammmmm

@pikammmmm pikammmmm commented Jul 30, 2026

Copy link
Copy Markdown

CompatibilityFiles.SpecPath, .ChildPath and .OrphanPath each defined joinpath() with a single required argument, but importlib.resources.abc.Traversable declares joinpath(*descendants) and documents that passing no descendants returns self.

Since 3.13 the functional API is implemented on top of files(anchor).joinpath(*path_names), so any call that passes no path names reaches joinpath() with no arguments and raises TypeError for a package whose loader provides only a legacy ResourceReader:

3.12.13 3.13.13 / 3.14.4 / 3.15.0b1 this PR
contents(FakeModule) ['NAME'] TypeError ['NAME']
is_resource(FakeModule) n/a — required a name TypeError False

(measured with the reproducer from the issue; 3.12 predates _functional.py, whose one-name-per-call predecessor never routed through joinpath())

This is the fix @jaraco sketched in the issue, extended to ChildPath and OrphanPath as he suggested it might need to be. It also fixes the latent multi-argument case, broken ever since the protocol gained *descendants in 3.11 without _adapters.py being updated — as @FFY00 diagnosed, "the CompatibilityFiles implementation is bad, it can only ever take one argument, and now it needs to be able to take any number of arguments."

One deviation from the sketch: it used '/'.join(descendants) for SpecPath, which would make files.joinpath('a', 'b') a single ChildPath named a/b, while files / 'a' / 'b' is an OrphanPath('a', 'b'). The Traversable.joinpath docs present the chained single-argument form as the compatibility equivalent of the multi-argument form for implementations that predate it, so this PR applies descendants one at a time and the two forms agree. That also avoids '/'.join raising TypeError on a PathLike segment which single-argument joinpath() accepts today.

CompatibilityFiles still does not meaningfully support os.PathLike segments; that is unchanged here and out of scope.

Tests

Seven new tests in test_compatibilty_files.py: zero-argument joinpath() on all three path classes (with and without a reader), the multi-argument/chained equivalence, and contents()/is_resource() through the functional API.

  • Negative control: with _adapters.py reverted, all seven new tests fail and the 16 pre-existing tests in the file still pass.
  • All 221 tests in test_importlib.resources pass (1 skipped).
  • I test without a local C build — checkout Lib/ on a matching installed interpreter — which yields 88 unrelated pre-existing failures in test_importlib.metadata/frozen/extension. That set is identical with and without this patch, and none of them are in resources.

Sibling fix for the same protocol violation in simple.py: #145263.

@FFY00@jaraco assigned this to you in December 2024; happy to close this if you would rather take it yourself.

…aders

CompatibilityFiles.SpecPath, .ChildPath and .OrphanPath each defined
joinpath() with a single required argument, but
importlib.resources.abc.Traversable declares joinpath(*descendants) and
documents that passing no descendants returns self.

Since 3.13 the functional API is implemented on top of
files(anchor).joinpath(*path_names), so any call that passes no path names
-- contents(anchor), is_resource(anchor) -- raised TypeError for a package
whose loader provides only a legacy ResourceReader.  On 3.12,
contents(anchor) returned the resource names.

Passing several descendants at once now gives the same result as applying
them one at a time, which is the equivalence the Traversable documentation
relies on for implementations predating the multi-argument protocol.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant