summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/importlib.rst66
-rw-r--r--Lib/importlib/abc.py38
-rw-r--r--Lib/test/test_importlib/test_abc.py39
-rw-r--r--Misc/NEWS.d/next/Library/2017-12-15-15-34-12.bpo-32248.zmO8G2.rst2
4 files changed, 145 insertions, 0 deletions
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
index 3cafb41..eeccc9d 100644
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -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
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
index d7cadf2..b772db3 100644
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -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([])
diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py
index 4ba28c6..f1e1db3 100644
--- a/Lib/test/test_importlib/test_abc.py
+++ b/Lib/test/test_importlib/test_abc.py
@@ -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:
diff --git a/Misc/NEWS.d/next/Library/2017-12-15-15-34-12.bpo-32248.zmO8G2.rst b/Misc/NEWS.d/next/Library/2017-12-15-15-34-12.bpo-32248.zmO8G2.rst
new file mode 100644
index 0000000..f77cdb0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-12-15-15-34-12.bpo-32248.zmO8G2.rst
@@ -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.