bpo-32248: Implement importlib.abc.ResourceReader (GH-4892)
This commit is contained in:
parent
d2b02310ac
commit
4ac5150e06
|
@ -233,6 +233,7 @@ ABC hierarchy::
|
|||
| +-- MetaPathFinder
|
||||
| +-- PathEntryFinder
|
||||
+-- Loader
|
||||
+-- ResourceReader
|
||||
+-- ResourceLoader --------+
|
||||
+-- InspectLoader |
|
||||
+-- ExecutionLoader --+
|
||||
|
@ -468,6 +469,71 @@ ABC hierarchy::
|
|||
The import machinery now takes care of this automatically.
|
||||
|
||||
|
||||
.. class:: ResourceReader
|
||||
|
||||
An :term:`abstract base class` for :term:`package`
|
||||
:term:`loaders <loader>` to provide the ability to read
|
||||
*resources*.
|
||||
|
||||
From the perspective of this ABC, a *resource* is a binary
|
||||
artifact that is shipped within a package. Typically this is
|
||||
something like a data file that lives next to the ``__init__.py``
|
||||
file of the package. The purpose of this class is to help abstract
|
||||
out the accessing of such data files so that it does not matter if
|
||||
the package and its data file(s) are stored in a e.g. zip file
|
||||
versus on the file system.
|
||||
|
||||
For any of methods of this class, a *resource* argument is
|
||||
expected to be a :term:`file-like object` which represents
|
||||
conceptually just a file name. This means that no subdirectory
|
||||
paths should be included in the *resource* argument. This is
|
||||
because the location of the package that the loader is for acts
|
||||
as the "directory". Hence the metaphor for directories and file
|
||||
names is packages and resources, respectively. This is also why
|
||||
instances of this class are expected to directly correlate to
|
||||
a specific package (instead of potentially representing multiple
|
||||
packages or a module).
|
||||
|
||||
.. versionadded:: 3.7
|
||||
|
||||
.. abstractmethod:: open_resource(resource)
|
||||
|
||||
Returns an opened, :term:`file-like object` for binary reading
|
||||
of the *resource*.
|
||||
|
||||
If the resource cannot be found, :exc:`FileNotFoundError` is
|
||||
raised.
|
||||
|
||||
.. abstractmethod:: resource_path(resource)
|
||||
|
||||
Returns the file system path to the *resource*.
|
||||
|
||||
If the resource does not concretely exist on the file system,
|
||||
raise :exc:`FileNotFoundError`.
|
||||
|
||||
.. abstractmethod:: is_resource(name)
|
||||
|
||||
Returns ``True`` if the named *name* is considered a resource.
|
||||
:exc:`FileNotFoundError` is raised if *name* does not exist.
|
||||
|
||||
.. abstractmethod:: contents()
|
||||
|
||||
Returns an :term:`iterator` of strings over the contents of
|
||||
the package. Do note that it is not required that all names
|
||||
returned by the iterator be actual resources, e.g. it is
|
||||
acceptable to return names for which :meth:`is_resource` would
|
||||
be false.
|
||||
|
||||
Allowing non-resource names to be returned is to allow for
|
||||
situations where how a package and its resources are stored
|
||||
are known a priori and the non-resource names would be useful.
|
||||
For instance, returning subdirectory names is allowed so that
|
||||
when it is known that the package and resources are stored on
|
||||
the file system then those subdirectory names can be used.
|
||||
|
||||
The abstract method returns an empty iterator.
|
||||
|
||||
|
||||
.. class:: ResourceLoader
|
||||
|
||||
An abstract base class for a :term:`loader` which implements the optional
|
||||
|
|
|
@ -340,3 +340,41 @@ class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLo
|
|||
"""
|
||||
|
||||
_register(SourceLoader, machinery.SourceFileLoader)
|
||||
|
||||
|
||||
class ResourceReader(Loader):
|
||||
|
||||
"""Abstract base class for loaders to provide resource reading support."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def open_resource(self, resource):
|
||||
"""Return an opened, file-like object for binary reading.
|
||||
|
||||
The 'resource' argument is expected to represent only a file name
|
||||
and thus not contain any subdirectory components.
|
||||
|
||||
If the resource cannot be found, FileNotFoundError is raised.
|
||||
"""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def resource_path(self, resource):
|
||||
"""Return the file system path to the specified resource.
|
||||
|
||||
The 'resource' argument is expected to represent only a file name
|
||||
and thus not contain any subdirectory components.
|
||||
|
||||
If the resource does not exist on the file system, raise
|
||||
FileNotFoundError.
|
||||
"""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_resource(self, name):
|
||||
"""Return True if the named 'name' is consider a resource."""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def contents(self):
|
||||
"""Return an iterator of strings over the contents of the package."""
|
||||
return iter([])
|
||||
|
|
|
@ -305,6 +305,45 @@ class ExecutionLoaderDefaultsTests(ABCTestHarness):
|
|||
) = test_util.test_both(InspectLoaderDefaultsTests)
|
||||
|
||||
|
||||
class ResourceReader:
|
||||
|
||||
def open_resource(self, *args, **kwargs):
|
||||
return super().open_resource(*args, **kwargs)
|
||||
|
||||
def resource_path(self, *args, **kwargs):
|
||||
return super().resource_path(*args, **kwargs)
|
||||
|
||||
def is_resource(self, *args, **kwargs):
|
||||
return super().is_resource(*args, **kwargs)
|
||||
|
||||
def contents(self, *args, **kwargs):
|
||||
return super().contents(*args, **kwargs)
|
||||
|
||||
|
||||
class ResourceReaderDefaultsTests(ABCTestHarness):
|
||||
|
||||
SPLIT = make_abc_subclasses(ResourceReader)
|
||||
|
||||
def test_open_resource(self):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
self.ins.open_resource('dummy_file')
|
||||
|
||||
def test_resource_path(self):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
self.ins.resource_path('dummy_file')
|
||||
|
||||
def test_is_resource(self):
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
self.ins.is_resource('dummy_file')
|
||||
|
||||
def test_contents(self):
|
||||
self.assertEqual([], list(self.ins.contents()))
|
||||
|
||||
(Frozen_RRDefaultTests,
|
||||
Source_RRDefaultsTests
|
||||
) = test_util.test_both(ResourceReaderDefaultsTests)
|
||||
|
||||
|
||||
##### MetaPathFinder concrete methods ##########################################
|
||||
class MetaPathFinderFindModuleTests:
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Add :class:`importlib.abc.ResourceReader` as an ABC for loaders to provide a
|
||||
unified API for reading resources contained within packages.
|
Loading…
Reference in New Issue