|
4 | 4 | Using importlib_resources
|
5 | 5 | ===========================
|
6 | 6 |
|
| 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 | + |
7 | 69 | Let's say you are writing an email parsing library and in your test suite you
|
8 | 70 | have a sample email message in a file called ``message.eml``. You would like
|
9 | 71 | to access the contents of this file for your tests, so you put this in your
|
@@ -57,7 +119,7 @@ Packages or package names
|
57 | 119 |
|
58 | 120 | All of the ``importlib_resources`` APIs take a *package* as their first
|
59 | 121 | 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 |
61 | 123 | passed in, it must name an importable Python package, and this is first
|
62 | 124 | imported. Thus the above example could also be written as::
|
63 | 125 |
|
@@ -91,10 +153,22 @@ manager.
|
91 | 153 |
|
92 | 154 | .. rubric:: Footnotes
|
93 | 155 |
|
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 |
95 | 168 | have an ``__path__`` attribute, while in Python 3, the module's
|
96 | 169 | ``__spec__.submodule_search_locations`` must not be ``None``.
|
97 | 170 | Otherwise a ``TypeError`` is raised.
|
98 | 171 |
|
99 | 172 |
|
100 | 173 | .. _`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