Skip to content

Commit cef6df8

Browse files
committed
Provide background on the philosophy of importlib_resources
Closes python#43
1 parent afe14c3 commit cef6df8

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

importlib_resources/docs/abc.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _abc:
2+
13
========================
24
The ResourceReader ABC
35
========================

importlib_resources/docs/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _api:
2+
13
=========================
24
importlib_resources API
35
=========================

importlib_resources/docs/using.rst

+76-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,68 @@
44
Using importlib_resources
55
===========================
66

7+
``importlib_resources`` is a library that leverages Python's import system to
8+
provide access to *resources* within *packages*. Given that this library is
9+
built on top of the import system, it is highly efficient and easy to use.
10+
This library's philosophy is that, if you can import a package, you can access
11+
resources within that package. Resources can be opened or read, in either
12+
binary or text mode.
13+
14+
What exactly do we mean by "a resource"? It's easiest to think about the
15+
metaphor of files and directories on the file system, though it's important to
16+
keep in mind that this is just a metaphor. Resources and packages **do not**
17+
have to exist as physical files and directories on the file system.
18+
19+
If you have a file system layout such as::
20+
21+
data/
22+
__init__.py
23+
one/
24+
__init__.py
25+
resource1.txt
26+
two/
27+
__init__.py
28+
resource2.txt
29+
30+
then the directories are ``data``, ``data/one``, and ``data/two``. Each of
31+
these are also Python packages by virtue of the fact that they all contain
32+
``__init__.py`` files [#fn1]_. That means that in Python, all of these import
33+
statements work::
34+
35+
import data
36+
import data.one
37+
from data import two
38+
39+
Each import statement gives you a Python *module* corresponding to the
40+
``__init__.py`` file in each of the respective directories. These modules are
41+
packages since packages are just special module instances that have an
42+
additional attribute, namely a ``__path__`` [#fn2]_.
43+
44+
In this analogy then, resources are just files within a package directory, so
45+
``data/one/resource1.txt`` and ``data/two/resource2.txt`` are both resources,
46+
as are the ``__init__.py`` files in all the directories. However the package
47+
directories themselves are *not* resources; anything that contains other
48+
things (i.e. directories) are not themselves resources.
49+
50+
Resources are always accessed relative to the package that they live in. You
51+
cannot access a resource within a subdirectory inside a package. This means
52+
that ``resource1.txt`` is a resource within the ``data.one`` package, but
53+
neither ``resource2.txt`` nor ``two/resource2.txt`` are resources within the
54+
``data`` package. If a directory isn't a package, it can't be imported and
55+
thus can't contain resources.
56+
57+
Even when this hierarchical structure isn't represented by physical files and
58+
directories, the model still holds. So zip files can contain packages and
59+
resources, as could databases or other storage medium. In fact, while
60+
``importlib_resources`` supports physical file systems and zip files by
61+
default, anything that can be loaded with a Python import system `loader`_ can
62+
provide resources, as long as the loader implements the :ref:`ResourceReader
63+
<abc>` abstract base class.
64+
65+
66+
Example
67+
=======
68+
769
Let's say you are writing an email parsing library and in your test suite you
870
have a sample email message in a file called ``message.eml``. You would like
971
to access the contents of this file for your tests, so you put this in your
@@ -57,7 +119,7 @@ Packages or package names
57119

58120
All of the ``importlib_resources`` APIs take a *package* as their first
59121
parameter, but this can either be a package name (as a ``str``) or an actual
60-
module object, though the module *must* be a package [#fn1]_. If a string is
122+
module object, though the module *must* be a package [#fn3]_. If a string is
61123
passed in, it must name an importable Python package, and this is first
62124
imported. Thus the above example could also be written as::
63125

@@ -91,10 +153,22 @@ manager.
91153

92154
.. rubric:: Footnotes
93155

94-
.. [#fn1] Specifically, this means that in Python 2, the module object must
156+
.. [#fn1] We're ignoring `PEP 420
157+
<https://www.python.org/dev/peps/pep-0420/>`_ style namespace
158+
packages, since ``importlib_resources`` does not support resources
159+
within namespace packages. Also, the example assumes that the
160+
parent directory containing ``data/`` is on ``sys.path``.
161+
162+
.. [#fn2] As of `PEP 451 <https://www.python.org/dev/peps/pep-0451/>`_ this
163+
information is also available on the module's
164+
``__spec__.submodule_search_locations`` attribute, which will not be
165+
``None`` for packages.
166+
167+
.. [#fn3] Specifically, this means that in Python 2, the module object must
95168
have an ``__path__`` attribute, while in Python 3, the module's
96169
``__spec__.submodule_search_locations`` must not be ``None``.
97170
Otherwise a ``TypeError`` is raised.
98171
99172
100173
.. _`pkg_resources API`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
174+
.. _`loader`: https://docs.python.org/3/reference/import.html#finders-and-loaders

0 commit comments

Comments
 (0)