Skip to content

Commit 63a7bcb

Browse files
authored
Merge pull request #315 from python/bugfix/311-non-path-namespace-paths
Omit non-dir items when constructing a NamespaceReader
2 parents 4875bc5 + 2c145c5 commit 63a7bcb

File tree

3 files changed

+29
-4
lines changed

3 files changed

+29
-4
lines changed

importlib_resources/readers.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,23 @@ class NamespaceReader(abc.TraversableResources):
138138
def __init__(self, namespace_path):
139139
if 'NamespacePath' not in str(namespace_path):
140140
raise ValueError('Invalid path')
141-
self.path = MultiplexedPath(*map(self._resolve, namespace_path))
141+
self.path = MultiplexedPath(*filter(bool, map(self._resolve, namespace_path)))
142142

143143
@classmethod
144-
def _resolve(cls, path_str) -> abc.Traversable:
144+
def _resolve(cls, path_str) -> abc.Traversable | None:
145145
r"""
146146
Given an item from a namespace path, resolve it to a Traversable.
147147
148148
path_str might be a directory on the filesystem or a path to a
149149
zipfile plus the path within the zipfile, e.g. ``/foo/bar`` or
150150
``/foo/baz.zip/inner_dir`` or ``foo\baz.zip\inner_dir\sub``.
151+
152+
path_str might also be a sentinel used by editable packages to
153+
trigger other behaviors (see python/importlib_resources#311).
154+
In that case, return None.
151155
"""
152-
(dir,) = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
153-
return dir
156+
dirs = (cand for cand in cls._candidate_paths(path_str) if cand.is_dir())
157+
return next(dirs, None)
154158

155159
@classmethod
156160
def _candidate_paths(cls, path_str: str) -> Iterator[abc.Traversable]:

importlib_resources/tests/test_files.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
6060
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
6161
MODULE = 'namespacedata01'
6262

63+
def test_non_paths_in_dunder_path(self):
64+
"""
65+
Non-path items in a namespace package's ``__path__`` are ignored.
66+
67+
As reported in python/importlib_resources#311, some tools
68+
like Setuptools, when creating editable packages, will inject
69+
non-paths into a namespace package's ``__path__``, a
70+
sentinel like
71+
``__editable__.sample_namespace-1.0.finder.__path_hook__``
72+
to cause the ``PathEntryFinder`` to be called when searching
73+
for packages. In that case, resources should still be loadable.
74+
"""
75+
import namespacedata01
76+
77+
namespacedata01.__path__.append(
78+
'__editable__.sample_namespace-1.0.finder.__path_hook__'
79+
)
80+
81+
resources.files(namespacedata01)
82+
6383

6484
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
6585
ZIP_MODULE = 'namespacedata01'

newsfragments/311.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Omit sentinel values from a namespace path.

0 commit comments

Comments
 (0)