mirror of
https://github.com/python/cpython.git
synced 2025-10-01 04:42:10 +00:00
bpo-33254: do not return an empty list when asking for the contents of a namespace package (GH-6467)
(cherry picked from commit 3ab9365dca
)
Co-authored-by: Brett Cannon <brettcannon@users.noreply.github.com>
This commit is contained in:
parent
887b5f8fc6
commit
2e5fa38c5a
5 changed files with 22 additions and 21 deletions
|
@ -530,7 +530,7 @@ ABC hierarchy::
|
||||||
|
|
||||||
.. abstractmethod:: contents()
|
.. abstractmethod:: contents()
|
||||||
|
|
||||||
Returns an :term:`iterator` of strings over the contents of
|
Returns an :term:`iterable` of strings over the contents of
|
||||||
the package. Do note that it is not required that all names
|
the package. Do note that it is not required that all names
|
||||||
returned by the iterator be actual resources, e.g. it is
|
returned by the iterator be actual resources, e.g. it is
|
||||||
acceptable to return names for which :meth:`is_resource` would
|
acceptable to return names for which :meth:`is_resource` would
|
||||||
|
@ -544,7 +544,7 @@ ABC hierarchy::
|
||||||
the file system then those subdirectory names can be used
|
the file system then those subdirectory names can be used
|
||||||
directly.
|
directly.
|
||||||
|
|
||||||
The abstract method returns an iterator of no items.
|
The abstract method returns an iterable of no items.
|
||||||
|
|
||||||
|
|
||||||
.. class:: ResourceLoader
|
.. class:: ResourceLoader
|
||||||
|
@ -926,9 +926,9 @@ The following functions are available.
|
||||||
|
|
||||||
.. function:: contents(package)
|
.. function:: contents(package)
|
||||||
|
|
||||||
Return an iterator over the named items within the package. The iterator
|
Return an iterable over the named items within the package. The iterable
|
||||||
returns :class:`str` resources (e.g. files) and non-resources
|
returns :class:`str` resources (e.g. files) and non-resources
|
||||||
(e.g. directories). The iterator does not recurse into subdirectories.
|
(e.g. directories). The iterable does not recurse into subdirectories.
|
||||||
|
|
||||||
*package* is either a name or a module object which conforms to the
|
*package* is either a name or a module object which conforms to the
|
||||||
``Package`` requirements.
|
``Package`` requirements.
|
||||||
|
|
|
@ -381,8 +381,8 @@ class ResourceReader(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def contents(self):
|
def contents(self):
|
||||||
"""Return an iterator of strings over the contents of the package."""
|
"""Return an iterable of strings over the contents of the package."""
|
||||||
return iter([])
|
return []
|
||||||
|
|
||||||
|
|
||||||
_register(ResourceReader, machinery.SourceFileLoader)
|
_register(ResourceReader, machinery.SourceFileLoader)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from importlib.abc import ResourceLoader
|
||||||
from io import BytesIO, TextIOWrapper
|
from io import BytesIO, TextIOWrapper
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Iterator, Optional, Set, Union # noqa: F401
|
from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing.io import BinaryIO, TextIO
|
from typing.io import BinaryIO, TextIO
|
||||||
from zipimport import ZipImportError
|
from zipimport import ZipImportError
|
||||||
|
@ -44,8 +44,7 @@ def _normalize_path(path) -> str:
|
||||||
|
|
||||||
If the resulting string contains path separators, an exception is raised.
|
If the resulting string contains path separators, an exception is raised.
|
||||||
"""
|
"""
|
||||||
str_path = str(path)
|
parent, file_name = os.path.split(path)
|
||||||
parent, file_name = os.path.split(str_path)
|
|
||||||
if parent:
|
if parent:
|
||||||
raise ValueError('{!r} must be only a file name'.format(path))
|
raise ValueError('{!r} must be only a file name'.format(path))
|
||||||
else:
|
else:
|
||||||
|
@ -228,8 +227,8 @@ def is_resource(package: Package, name: str) -> bool:
|
||||||
return path.is_file()
|
return path.is_file()
|
||||||
|
|
||||||
|
|
||||||
def contents(package: Package) -> Iterator[str]:
|
def contents(package: Package) -> Iterable[str]:
|
||||||
"""Return the list of entries in 'package'.
|
"""Return an iterable of entries in 'package'.
|
||||||
|
|
||||||
Note that not all entries are resources. Specifically, directories are
|
Note that not all entries are resources. Specifically, directories are
|
||||||
not considered resources. Use `is_resource()` on each entry returned here
|
not considered resources. Use `is_resource()` on each entry returned here
|
||||||
|
@ -238,15 +237,15 @@ def contents(package: Package) -> Iterator[str]:
|
||||||
package = _get_package(package)
|
package = _get_package(package)
|
||||||
reader = _get_resource_reader(package)
|
reader = _get_resource_reader(package)
|
||||||
if reader is not None:
|
if reader is not None:
|
||||||
yield from reader.contents()
|
return reader.contents()
|
||||||
return
|
|
||||||
# Is the package a namespace package? By definition, namespace packages
|
# Is the package a namespace package? By definition, namespace packages
|
||||||
# cannot have resources. We could use _check_location() and catch the
|
# cannot have resources. We could use _check_location() and catch the
|
||||||
# exception, but that's extra work, so just inline the check.
|
# exception, but that's extra work, so just inline the check.
|
||||||
if package.__spec__.origin is None or not package.__spec__.has_location:
|
elif package.__spec__.origin is None or not package.__spec__.has_location:
|
||||||
return []
|
return ()
|
||||||
package_directory = Path(package.__spec__.origin).parent
|
else:
|
||||||
yield from os.listdir(str(package_directory))
|
package_directory = Path(package.__spec__.origin).parent
|
||||||
|
return os.listdir(package_directory)
|
||||||
|
|
||||||
|
|
||||||
# Private implementation of ResourceReader and get_resource_reader() for
|
# Private implementation of ResourceReader and get_resource_reader() for
|
||||||
|
|
|
@ -131,10 +131,9 @@ class ResourceFromZipsTest(util.ZipSetupBase, unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class NamespaceTest(unittest.TestCase):
|
class NamespaceTest(unittest.TestCase):
|
||||||
def test_namespaces_cant_have_resources(self):
|
def test_namespaces_cannot_have_resources(self):
|
||||||
contents = set(resources.contents(
|
contents = resources.contents('test.test_importlib.data03.namespace')
|
||||||
'test.test_importlib.data03.namespace'))
|
self.assertFalse(list(contents))
|
||||||
self.assertEqual(len(contents), 0)
|
|
||||||
# Even though there is a file in the namespace directory, it is not
|
# Even though there is a file in the namespace directory, it is not
|
||||||
# considered a resource, since namespace packages can't have them.
|
# considered a resource, since namespace packages can't have them.
|
||||||
self.assertFalse(resources.is_resource(
|
self.assertFalse(resources.is_resource(
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Have :func:`importlib.resources.contents` and
|
||||||
|
:meth:`importlib.abc.ResourceReader.contents` return an :term:`iterable` instead
|
||||||
|
of an :term:`iterator`.
|
Loading…
Add table
Add a link
Reference in a new issue