Skip to content

Commit 2e5fa38

Browse files
bpo-33254: do not return an empty list when asking for the contents of a namespace package (GH-6467)
(cherry picked from commit 3ab9365) Co-authored-by: Brett Cannon <brettcannon@users.noreply.github.com>
1 parent 887b5f8 commit 2e5fa38

File tree

5 files changed

+22
-21
lines changed

5 files changed

+22
-21
lines changed

Doc/library/importlib.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ ABC hierarchy::
530530

531531
.. abstractmethod:: contents()
532532

533-
Returns an :term:`iterator` of strings over the contents of
533+
Returns an :term:`iterable` of strings over the contents of
534534
the package. Do note that it is not required that all names
535535
returned by the iterator be actual resources, e.g. it is
536536
acceptable to return names for which :meth:`is_resource` would
@@ -544,7 +544,7 @@ ABC hierarchy::
544544
the file system then those subdirectory names can be used
545545
directly.
546546

547-
The abstract method returns an iterator of no items.
547+
The abstract method returns an iterable of no items.
548548

549549

550550
.. class:: ResourceLoader
@@ -926,9 +926,9 @@ The following functions are available.
926926

927927
.. function:: contents(package)
928928

929-
Return an iterator over the named items within the package. The iterator
929+
Return an iterable over the named items within the package. The iterable
930930
returns :class:`str` resources (e.g. files) and non-resources
931-
(e.g. directories). The iterator does not recurse into subdirectories.
931+
(e.g. directories). The iterable does not recurse into subdirectories.
932932

933933
*package* is either a name or a module object which conforms to the
934934
``Package`` requirements.

Lib/importlib/abc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,8 @@ def is_resource(self, name):
381381

382382
@abc.abstractmethod
383383
def contents(self):
384-
"""Return an iterator of strings over the contents of the package."""
385-
return iter([])
384+
"""Return an iterable of strings over the contents of the package."""
385+
return []
386386

387387

388388
_register(ResourceReader, machinery.SourceFileLoader)

Lib/importlib/resources.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from io import BytesIO, TextIOWrapper
1010
from pathlib import Path
1111
from types import ModuleType
12-
from typing import Iterator, Optional, Set, Union # noqa: F401
12+
from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
1313
from typing import cast
1414
from typing.io import BinaryIO, TextIO
1515
from zipimport import ZipImportError
@@ -44,8 +44,7 @@ def _normalize_path(path) -> str:
4444
4545
If the resulting string contains path separators, an exception is raised.
4646
"""
47-
str_path = str(path)
48-
parent, file_name = os.path.split(str_path)
47+
parent, file_name = os.path.split(path)
4948
if parent:
5049
raise ValueError('{!r} must be only a file name'.format(path))
5150
else:
@@ -228,8 +227,8 @@ def is_resource(package: Package, name: str) -> bool:
228227
return path.is_file()
229228

230229

231-
def contents(package: Package) -> Iterator[str]:
232-
"""Return the list of entries in 'package'.
230+
def contents(package: Package) -> Iterable[str]:
231+
"""Return an iterable of entries in 'package'.
233232
234233
Note that not all entries are resources. Specifically, directories are
235234
not considered resources. Use `is_resource()` on each entry returned here
@@ -238,15 +237,15 @@ def contents(package: Package) -> Iterator[str]:
238237
package = _get_package(package)
239238
reader = _get_resource_reader(package)
240239
if reader is not None:
241-
yield from reader.contents()
242-
return
240+
return reader.contents()
243241
# Is the package a namespace package? By definition, namespace packages
244242
# cannot have resources. We could use _check_location() and catch the
245243
# exception, but that's extra work, so just inline the check.
246-
if package.__spec__.origin is None or not package.__spec__.has_location:
247-
return []
248-
package_directory = Path(package.__spec__.origin).parent
249-
yield from os.listdir(str(package_directory))
244+
elif package.__spec__.origin is None or not package.__spec__.has_location:
245+
return ()
246+
else:
247+
package_directory = Path(package.__spec__.origin).parent
248+
return os.listdir(package_directory)
250249

251250

252251
# Private implementation of ResourceReader and get_resource_reader() for

Lib/test/test_importlib/test_resource.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,9 @@ def test_submodule_contents_by_name(self):
131131

132132

133133
class NamespaceTest(unittest.TestCase):
134-
def test_namespaces_cant_have_resources(self):
135-
contents = set(resources.contents(
136-
'test.test_importlib.data03.namespace'))
137-
self.assertEqual(len(contents), 0)
134+
def test_namespaces_cannot_have_resources(self):
135+
contents = resources.contents('test.test_importlib.data03.namespace')
136+
self.assertFalse(list(contents))
138137
# Even though there is a file in the namespace directory, it is not
139138
# considered a resource, since namespace packages can't have them.
140139
self.assertFalse(resources.is_resource(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Have :func:`importlib.resources.contents` and
2+
:meth:`importlib.abc.ResourceReader.contents` return an :term:`iterable` instead
3+
of an :term:`iterator`.

0 commit comments

Comments
 (0)