Repository navigation
Can't open orphan path on bare importlib.resources.files() under zipapp #121735
Description
Bug report
Bug description:
Context: In Python 3.12 the function importlib.resources.files() was updated: https://docs.python.org/3/library/importlib.resources.html#importlib.resources.files
Changed in version 3.12: package parameter was renamed to anchor. anchor can now be a non-package module and if omitted will default to the caller’s module. package is still accepted for compatibility but will raise a DeprecationWarning. Consider passing the anchor positionally or using importlib_resources >= 5.10 for a compatible interface on older Pythons.
The Issue: When using importlib.resources.files(), I am able to open and read a text file included as package data in my package. However, if I zip up my package with zipapp and run the .pyz file, it is unable to read the text file.
If I use importlib.resources.files("mypkg") it will work in both cases.
Minimal example:
Create a project directory named mypkg with the src layout.
Create the following files.
mypkg/src/mypkg/a.py
import importlib.resources
def main():
x = importlib.resources.files() / "data.txt"
print(x.read_text())mypkg/src/mypkg/data.txt
datadata
In pyproject.toml make sure to set up the entry script and include the data file.
mypkg/pyproject.toml
[project.scripts]
myscript = "mypkg.a:main"
[tool.setuptools.package-data]
mypkg = ["*.txt"]
Install the package into a fresh Python (virtual) environment with pip.
Run myscript and verify it prints datadata.
Now back in the mypkg project root folder, we will generate a .pyz file with zipapp and run the program from the .pyz file.
Run pip install . --target buildtemp --upgrade && python3 -m zipapp buildtemp --main mypkg.a:main -p '/usr/bin/env python3' -o mypkg.pyz in the terminal.
Run ./mypkg.pyz. This results in an error:
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/home/win/code/sandbox/python/mypkg/./mypkg.pyz/__main__.py", line 3, in <module>
File "/home/win/code/sandbox/python/mypkg/./mypkg.pyz/mypkg/a.py", line 6, in main
File "/home/win/.local/share/mise/installs/python/3.12.4/lib/python3.12/importlib/resources/abc.py", line 89, in read_text
with self.open(encoding=encoding) as strm:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/win/.local/share/mise/installs/python/3.12.4/lib/python3.12/importlib/resources/_adapters.py", line 139, in open
raise FileNotFoundError("Can't open orphan path")
FileNotFoundError: Can't open orphan path
Note that if you change x = importlib.resources.files() / "data.txt" to x = importlib.resources.files("mypkg") / "data.txt", it will work when running the .pyz file.
CPython versions tested on:
3.12
Operating systems tested on:
Linux, macOS
Activity
Pin irc library version to 20.4.0
wimglenn commentedon Aug 14, 2024
ping @jaraco in case you missed this one
jaraco commentedon Aug 14, 2024
The code that handles the module inference is here:
cpython/Lib/importlib/resources/_common.py
Lines 75 to 104 in 6ae942f
Next step is to determine what expectation is missed when running under zipapp.
[-]Cannot read package data file after opening with importlib.resources.files(anchor) and running with zipapp[/-][+]Can't open orphan path on bare importlib.resources.files() under zipapp[/+]jaraco commentedon Aug 14, 2024
I was able to replicate the issue without going through the wheel build process:
jaraco commentedon Aug 14, 2024
The issue is that
CompatibilityFilesis being activated for loading resources.CompatibilityFilesgets employed when there's no proper resource provider found. I'll figure out why.jaraco commentedon Aug 14, 2024
It's clear that the bare call to
files()is gettingCompatibiltyFileswhen the call with an anchor returns a properMultiplexedPath:By changing the
main()function to emit the inferred caller's name,I can confirm that the inferred name is correct when it emits
mypkg.a.Interestingly, if I then replace the main function with a static 'mypkg.a', the reported error occurs.
So the issue seems to be that
files('mypkg')works as expected butfiles('mypkg.a')does not, and it's only coincidental thatfiles()callsfiles('mypkg.a').importlib.resourceshas a behavior that when given a module will resolve resources in the package of that module.It seems that the
zipimporteras attached to the module is not resolving a resource reader:I'm slightly surprised to see
NamespaceReaderand not aZipReader.jaraco commentedon Aug 14, 2024
It seems the issue traces to the logic in get_resource_reader:
cpython/Lib/zipimport.py
Lines 258 to 270 in 6ae942f
When checking
loader.is_packageformypkg.a, it returns False:In the early days of importlib.resources, all resources needed to be relative to a package (and not a module). Later, that restriction was relaxed to allow resources to be loaded relative to a module.
But it seems that
get_resource_readerdidn't get the corresponding treatment.I've confirmed the same issue occurs when using
importlib_resources:I suspect the resolution is going to be update
get_resource_readerto accept a module.But first things first, let's figure out why this use-case isn't covered by the tests.
jaraco commentedon Aug 14, 2024
As a check, if I remove the
is_packagecheck:The routine then fails, but with a different error:
That's encouraging, suggesting that the
ZipReaderis getting involved, but that it still doesn't honor resources adjacent to modules.jaraco commentedon Aug 14, 2024
I started working on a test, seeking to extend these tests to zip files, but realized that the current test structure isn't easily amenable to extension to zip. In python/importlib_resources@3faf336, I've done some refactoring in order to leverage the zip fixtures for this purpose.
Expand ModuleFiles and ImplicitContextFiles tests to include zip files.
Expand ModuleFiles and ImplicitContextFiles tests to include zip files.
16 remaining items
chore(deps): Bump importlib-resources from 6.4.0 to 6.4.3 (#1971)
Bump importlib-resources from 6.1.1 to 6.4.3 (#8806)
Bump importlib-resources from 6.4.0 to 6.4.3 (#398)
Bump importlib-resources from 6.1.1 to 6.4.4 (#8915)
Bump importlib-resources from 5.13.0 to 6.4.4 (#91)
gh-121735: Fix module-adjacent references in zip files (#123037)
pythongh-121735: Fix module-adjacent references in zip files (pythonG…
[3.12] pythongh-121735: Fix module-adjacent references in zip files (p…
[3.12] pythongh-121735: Fix module-adjacent references in zip files (p…
[3.12] pythongh-121735: Fix module-adjacent references in zip files
[3.12] pythongh-121735: Fix module-adjacent references in zip files
[3.12] gh-121735: Fix module-adjacent references in zip files (gh-123037
[3.13] gh-121735: Fix module-adjacent references in zip files (GH-123037
jaraco commentedon Dec 24, 2024
PRs are merged; closing.