Repository navigation
Calling importlib_resources.files with an editable install fails #311
Description
When calling importlib_resources.files("sample-namespace") to retrieve a text file, an exception is thrown if an editable install has been done for the package.
I am able to trigger this with a relatively simple setup - I have a namespace package with two files, sample.txt and a __main__.py that has two lines:
from importlib_resources import files
files("sample-namespace")
In a venv with only importlib_resources 6.4.0 installed, calling "py -m sample-namespace" works without issue if sample-namespace is not installed or normally installed, but will fail if it is installed in editable mode.
The error is thrown in the MultiplexedPath constructor, because '__editable__.sample_namespace-1.0.finder.__path_hook__' is one of the paths provided, and path.is_dir() is False leading to raising NotADirectoryError.
I'm not convinced that I'm doing everything right, but I don't know of any limitations with editable installs. I'm not very familiar with this library, but if there's anything I can do to help, let me know.
Thank you!
(.venv) PS C:\git\TEST\test-importlib-resources-files-editable> py -m sample-namespace
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "C:\git\TEST\test-importlib-resources-files-editable\sample-namespace\__main__.py", line 3, in <module>
files("sample-namespace")
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_common.py", line 46, in wrapper
return func(anchor)
^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_common.py", line 56, in files
return from_package(resolve(anchor))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_common.py", line 116, in from_package
reader = spec.loader.get_resource_reader(spec.name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\future\adapters.py", line 66, in get_resource_reader
or super().get_resource_reader(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_adapters.py", line 29, in get_resource_reader
return CompatibilityFiles(self.spec)._native()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_adapters.py", line 153, in _native
reader = self._reader
^^^^^^^^^^^^
File "C:\git\TEST\test-importlib-resources-files-editable\.venv\Lib\site-packages\importlib_resources\_adapters.py", line 147, in _reader
return self.spec.loader.get_resource_reader(self.spec.name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap_external>", line 1425, in get_resource_reader
File "C:\Users\garrstau\AppData\Local\Programs\Python\Python312\Lib\importlib\resources\readers.py", line 133, in __init__
self.path = MultiplexedPath(*list(namespace_path))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\garrstau\AppData\Local\Programs\Python\Python312\Lib\importlib\resources\readers.py", line 70, in __init__
raise NotADirectoryError('MultiplexedPath only supports directories')
NotADirectoryError: MultiplexedPath only supports directories
Activity
jaraco commentedon Aug 19, 2024
I recently encountered this error too in jaraco.imaging. The same code previously passed on Apr 24, so my guess is there was some regression since then.
jaraco commentedon Aug 19, 2024
For what it's worth, editable installs used to work, but not since the path hook was added to the namespace path (probably a newer form of editable install).
Here's what I'm seeing for namespace path:
@abravalheri Do you know what it is that's adding that
__editable__entry to the_NamespacePath? Is there a spec that allows that path to be something other than an importable path? What shouldimportlib_resourcesbe doing (if anything) to honor that path entry?jaraco commentedon Aug 20, 2024
Oh, I see pypa/setuptools#3548 is already tracking the issue.
abravalheri commentedon Aug 20, 2024
This is added by a
.pthfile added on purpose by theeditable_wheelcommand in setuptools to the editable wheel. The objective is to trigger aPathEntryFinder, which is so far the only known way in the community to implement namespace packages via import finders. The entry added tosys.pathis a path item... but not a conventional one: it is a path that does not exist but is there only to force thePathEntryFinderto be called.I believe that the standard that supports this is PEP 302. In the text of the PEP it is suggested that adding a "non existing" path is an acceptable approach for a different class of problems (but nonetheless the approach described still adds a non existing file to
sys.path):The Python docs also explicitly say:
Also here in https://docs.python.org/3/reference/import.html#finders-and-loaders:
So my interpretation is that
sys.pathcan contain all sorts of stuff that are searched by finders/loaders. The text even mentions database queries, which is a pretty good example of not a regular file path. Nevertheless, since there is an associatedPathEntryFinder, these entries work effectively as an "importable path" but not in the conventional sense.I am not sure here. PEP 320 mentions
loader.get_data(path). I did not implement any custom loader insetuptools, just the finders that rely on standard functions of importlib, so all the loaders produced should also be fairly standard. Maybe the standard loaders of importlib implement theget_dataprotocol?Another thought is that sometimes editable namespace packages do not correspond to an actual directory in disk, and it is only simulated by the importlib machinery. For example, consider that a developer uses
package_dirin setuptools to remap the file paths and point packagea.b.cto a project folder calledx. We can mapa.b.cto the existingxfolder, but there is no correspondence foraora.b.In that case, users are better off if they avoid calling
importlib_resources.files("a"). Instead, they will have to be more specific and callimportlib_resources.files("a.b.c").I believe that pypa/setuptools#3548 regards a different original problem.
To be honest, since we never got a reproducer, I believe that the original problem might be caused by another piece of software that expects all entries of
sys.pathto be existing regular paths to files or directories (and as explained above that is not how it is described by the Python docs). And if that is the case there is not much to be done by setuptools itself.abravalheri commentedon Aug 20, 2024
@GarrStau , there is a non-exhaustive list in https://setuptools.pypa.io/en/latest/userguide/development_mode.html#limitations.
jaraco commentedon Aug 20, 2024
That helps a lot. Thanks for the context. I do believe it makes sense for importlib resources to support this mode, and it should continue to be possible to resolve resources from a namespace package (as long as some provider provides resources from that package).
Add test capturing failure when resolving the MultiplexedPath for a n…
jaraco commentedon Aug 20, 2024
It wasn't too hard to create a test that captures the failure (b06b5fb), but unfortunately, its traceback reveals another problem:
The error is coming from
importlib.resources, meaning thatimportlib_resources' readers aren't taking precedence, so it won't be possible to fix the issue strictly in importlib_resources without first making sure that the local readers take precedence.jaraco commentedon Aug 20, 2024
I do see that
importlib_resources.future.adapters.TraversableResourcesLoader.get_resource_readeris getting tosuper().get_resource_reader(name), so._standard_reader()would have to have been called before that, so it's probably just the case that_namespace_readeris not resolving because of this reported issue. That is, there's probably aValueErroron line 149:importlib_resources/importlib_resources/readers.py
Lines 140 to 150 in d021417
And that's causing the namespace reader to be skipped.
So, fixing the root issue might also address the secondary issue.
Omit sentinel values from a namespace path.
jaraco commentedon Aug 20, 2024
And indeed it does.
Omit sentinel values from a namespace path.
Add test capturing failure when resolving the MultiplexedPath for a n…
Omit sentinel values from a namespace path.
USSX-Hares commentedon Jul 1, 2025
Will this be ported to 3.12, or will it only be present in 3.13?
Added helper function to get path to openghg/data
Added helper function to get path to openghg/data
Added helper function to get path to openghg/data
Added helper function to get path to openghg/data
Devel copy to merge in add cams parser (#1452)
Devel copy to merge in add cams parser (#1470)