File: migration.rst

package info (click to toggle)
importlib-resources 6.5.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 408 kB
  • sloc: python: 1,877; makefile: 4
file content (155 lines) | stat: -rw-r--r-- 5,908 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
.. _migration:

=================
 Migration guide
=================

The following guide will help you migrate common ``pkg_resources`` APIs to
``importlib_resources``.  Only a small number of the most common APIs are
supported by ``importlib_resources``, so projects that use other features
(e.g. entry points) will have to find other solutions.
``importlib_resources`` primarily supports the following `basic resource
access`_ APIs:

* ``pkg_resources.resource_filename()``
* ``pkg_resources.resource_stream()``
* ``pkg_resources.resource_string()``
* ``pkg_resources.resource_listdir()``
* ``pkg_resources.resource_isdir()``

Note that although the steps below provide a drop-in replacement for the
above methods, for many use-cases, a better approach is to use the
``Traversable`` path from ``files()`` directly.


pkg_resources.resource_filename()
=================================

``resource_filename()`` is one of the more interesting APIs because it
guarantees that the return value names a file on the file system.  This means
that if the resource is in a zip file, ``pkg_resources`` will extract the
file and return the name of the temporary file it created.  The problem is
that ``pkg_resources`` also *implicitly* cleans up this temporary file,
without control over its lifetime by the programmer.

``importlib_resources`` takes a different approach.  Its equivalent API is the
``files()`` function, which returns a Traversable object implementing a
subset of the
:py:class:`pathlib.Path` interface suitable for reading the contents and
provides a wrapper for creating a temporary file on the system in a
context whose lifetime is managed by the user.  Note though
that if the resource is *already* on the file system, ``importlib_resources``
still returns a context manager, but nothing needs to get cleaned up.

Here's an example from ``pkg_resources``::

    path = pkg_resources.resource_filename('my.package', 'resource.dat')

The best way to convert this is with the following idiom::

    ref = importlib_resources.files('my.package') / 'resource.dat'
    with importlib_resources.as_file(ref) as path:
        # Do something with path.  After the with-statement exits, any
        # temporary file created will be immediately cleaned up.

That's all fine if you only need the file temporarily, but what if you need it
to stick around for a while?  One way of doing this is to use an
:py:class:`contextlib.ExitStack` instance and manage the resource explicitly::

    from contextlib import ExitStack
    file_manager = ExitStack()
    ref = importlib_resources.files('my.package') / 'resource.dat'
    path = file_manager.enter_context(
        importlib_resources.as_file(ref))

Now ``path`` will continue to exist until you explicitly call
``file_manager.close()``.  What if you want the file to exist until the
process exits, or you can't pass ``file_manager`` around in your code?  Use an
:py:mod:`atexit` handler::

    import atexit
    file_manager = ExitStack()
    atexit.register(file_manager.close)
    ref = importlib_resources.files('my.package') / 'resource.dat'
    path = file_manager.enter_context(
        importlib_resources.as_file(ref))

Assuming your Python interpreter exits gracefully, the temporary file will be
cleaned up when Python exits.


pkg_resources.resource_stream()
===============================

``pkg_resources.resource_stream()`` returns a readable file-like object opened
in binary mode.  When you read from the returned file-like object, you get
bytes.  E.g.::

    with pkg_resources.resource_stream('my.package', 'resource.dat') as fp:
        my_bytes = fp.read()

The equivalent code in ``importlib_resources`` is pretty straightforward::

    ref = importlib_resources.files('my.package').joinpath('resource.dat')
    with ref.open('rb') as fp:
        my_bytes = fp.read()


pkg_resources.resource_string()
===============================

In Python 2, ``pkg_resources.resource_string()`` returns the contents of a
resource as a ``str``.  In Python 3, this function is a misnomer; it actually
returns the contents of the named resource as ``bytes``.  That's why the
following example is often written for clarity as::

    from pkg_resources import resource_string as resource_bytes
    contents = resource_bytes('my.package', 'resource.dat')

This can be easily rewritten like so::

    ref = importlib_resources.files('my.package').joinpath('resource.dat')
    contents = ref.read_bytes()


pkg_resources.resource_listdir()
================================

This function lists the entries in the package, both files and directories,
but it does not recurse into subdirectories, e.g.::

    for entry in pkg_resources.resource_listdir('my.package', 'subpackage'):
        print(entry)

This is easily rewritten using the following idiom::

    for entry in importlib_resources.files('my.package.subpackage').iterdir():
        print(entry.name)

Note:

* ``Traversable.iterdir()`` returns *all* the entries in the
  subpackage, i.e. both resources (files) and non-resources (directories).
* ``Traversable.iterdir()`` returns additional traversable objects, which if
  directories can also be iterated over (recursively).
* ``Traversable.iterdir()``, like ``pathlib.Path`` returns an iterator, not a
  concrete sequence.
* The order in which the elements are returned is undefined.


pkg_resources.resource_isdir()
==============================

You can ask ``pkg_resources`` to tell you whether a particular resource inside
a package is a directory or not::

    if pkg_resources.resource_isdir('my.package', 'resource'):
        print('A directory')

The ``importlib_resources`` equivalent is straightforward::

    if importlib_resources.files('my.package').joinpath('resource').is_dir():
        print('A directory')


.. _`basic resource access`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access