gh-127337: Fix importlib.resources for legacy ResourceReader loaders - #154919
gh-127337: Fix importlib.resources for legacy ResourceReader loaders#154919pikammmmm wants to merge 1 commit into
Conversation
…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.
|
@jaraco this is the diff from your comment on the issue, plus the One place I went a different way. You had
@zooba this is the "make the automatic adaptation get it right" path you argued for on the issue, in case you want to weigh in. |
CompatibilityFiles.SpecPath,.ChildPathand.OrphanPatheach definedjoinpath()with a single required argument, butimportlib.resources.abc.Traversabledeclaresjoinpath(*descendants)and documents that passing no descendants returnsself.Since 3.13 the functional API is implemented on top of
files(anchor).joinpath(*path_names), so any call that passes no path names reachesjoinpath()with no arguments and raisesTypeErrorfor a package whose loader provides only a legacyResourceReader:contents(FakeModule)['NAME']TypeError['NAME']is_resource(FakeModule)nameTypeErrorFalse(measured with the reproducer from the issue; 3.12 predates
_functional.py, whose one-name-per-call predecessor never routed throughjoinpath())This is the fix @jaraco sketched in the issue, extended to
ChildPathandOrphanPathas he suggested it might need to be. It also fixes the latent multi-argument case, broken ever since the protocol gained*descendantsin 3.11 without_adapters.pybeing updated — as @FFY00 diagnosed, "theCompatibilityFilesimplementation 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)forSpecPath, which would makefiles.joinpath('a', 'b')a singleChildPathnameda/b, whilefiles / 'a' / 'b'is anOrphanPath('a', 'b'). TheTraversable.joinpathdocs 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'/'.joinraisingTypeErroron aPathLikesegment which single-argumentjoinpath()accepts today.CompatibilityFilesstill does not meaningfully supportos.PathLikesegments; that is unchanged here and out of scope.Tests
Seven new tests in
test_compatibilty_files.py: zero-argumentjoinpath()on all three path classes (with and without a reader), the multi-argument/chained equivalence, andcontents()/is_resource()through the functional API._adapters.pyreverted, all seven new tests fail and the 16 pre-existing tests in the file still pass.test_importlib.resourcespass (1 skipped).Lib/on a matching installed interpreter — which yields 88 unrelated pre-existing failures intest_importlib.metadata/frozen/extension. That set is identical with and without this patch, and none of them are inresources.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.