using.rst 9.28 KB
Newer Older
1 2
.. _using:

3
=================================
4
 Using :mod:`!importlib_metadata`
5
=================================
6 7 8

``importlib_metadata`` is a library that provides for access to installed
package metadata.  Built in part on Python's import system, this library
9 10
intends to replace similar functionality in the `entry point
API`_ and `metadata API`_ of ``pkg_resources``.  Along with
11 12
:mod:`importlib.resources` in Python 3.7
and newer (backported as :doc:`importlib_resources <importlib_resources:index>` for older versions of
13 14 15
Python), this can eliminate the need to use the older and less efficient
``pkg_resources`` package.

16 17 18
By "installed package" we generally mean a third-party package installed into
Python's ``site-packages`` directory via tools such as `pip
<https://pypi.org/project/pip/>`_.  Specifically,
19
it means a package with either a discoverable ``dist-info`` or ``egg-info``
20
directory, and metadata defined by :pep:`566` or its older specifications.
21
By default, package metadata can live on the file system or in zip archives on
22
:data:`sys.path`.  Through an extension mechanism, the metadata can live almost
Barry Warsaw's avatar
Barry Warsaw committed
23
anywhere.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42


Overview
========

Let's say you wanted to get the version string for a package you've installed
using ``pip``.  We start by creating a virtual environment and installing
something into it::

    $ python3 -m venv example
    $ source example/bin/activate
    (example) $ pip install importlib_metadata
    (example) $ pip install wheel

You can get the version string for ``wheel`` by running the following::

    (example) $ python
    >>> from importlib_metadata import version
    >>> version('wheel')
43
    '0.32.3'
44

45 46 47
You can also get the set of entry points keyed by group, such as
``console_scripts``, ``distutils.commands`` and others.  Each group contains a
sequence of :ref:`EntryPoint <entry-points>` objects.
48

49
You can get the :ref:`metadata for a distribution <metadata>`::
50

51 52
    >>> list(metadata('wheel'))
    ['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License', 'Project-URL', 'Project-URL', 'Project-URL', 'Keywords', 'Platform', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Requires-Python', 'Provides-Extra', 'Requires-Dist', 'Requires-Dist']
53

54
You can also get a :ref:`distribution's version number <version>`, list its
Jason R. Coombs's avatar
Jason R. Coombs committed
55 56
:ref:`constituent files <files>`, and get a list of the distribution's
:ref:`requirements`.
57 58


59 60 61 62 63 64
Functional API
==============

This package provides the following functionality via its public API.


Jason R. Coombs's avatar
Jason R. Coombs committed
65
.. _entry-points:
66 67 68 69 70 71 72

Entry points
------------

The ``entry_points()`` function returns a dictionary of all entry points,
keyed by group.  Entry points are represented by ``EntryPoint`` instances;
each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
73 74 75
a ``.load()`` method to resolve the value.  There are also ``.module``,
``.attr``, and ``.extras`` attributes for getting the components of the
``.value`` attribute::
76 77 78 79 80 81 82 83

    >>> eps = entry_points()
    >>> list(eps)
    ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
    >>> scripts = eps['console_scripts']
    >>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0]
    >>> wheel
    EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
84 85 86 87 88 89
    >>> wheel.module
    'wheel.cli'
    >>> wheel.attr
    'main'
    >>> wheel.extras
    []
90 91 92 93 94 95 96 97
    >>> main = wheel.load()
    >>> main
    <function main at 0x103528488>

The ``group`` and ``name`` are arbitrary values defined by the package author
and usually a client will wish to resolve all entry points for a particular
group.  Read `the setuptools docs
<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
Brett Cannon's avatar
Brett Cannon committed
98
for more information on entry points, their definition, and usage.
99 100


Jason R. Coombs's avatar
Jason R. Coombs committed
101
.. _metadata:
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117

Distribution metadata
---------------------

Every distribution includes some metadata, which you can extract using the
``metadata()`` function::

    >>> wheel_metadata = metadata('wheel')

The keys of the returned data structure [#f1]_ name the metadata keywords, and
their values are returned unparsed from the distribution metadata::

    >>> wheel_metadata['Requires-Python']
    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'


Jason R. Coombs's avatar
Jason R. Coombs committed
118
.. _version:
119 120 121 122 123 124 125 126 127 128 129

Distribution versions
---------------------

The ``version()`` function is the quickest way to get a distribution's version
number, as a string::

    >>> version('wheel')
    '0.32.3'


Jason R. Coombs's avatar
Jason R. Coombs committed
130
.. _files:
131 132 133 134 135 136 137

Distribution files
------------------

You can also get the full set of files contained within a distribution.  The
``files()`` function takes a distribution package name and returns all of the
files installed by this distribution.  Each file object returned is a
138
``PackagePath``, a :class:`pathlib.Path` derived object with additional ``dist``,
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
``size``, and ``hash`` properties as indicated by the metadata.  For example::

    >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]
    >>> util
    PackagePath('wheel/util.py')
    >>> util.size
    859
    >>> util.dist
    <importlib_metadata._hooks.PathDistribution object at 0x101e0cef0>
    >>> util.hash
    <FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>

Once you have the file, you can also read its contents::

    >>> print(util.read_text())
    import base64
    import sys
    ...
    def as_bytes(s):
        if isinstance(s, text_type):
            return s.encode('utf-8')
        return s

162 163 164 165 166 167 168
In the case where the metadata file listing files
(RECORD or SOURCES.txt) is missing, ``files()`` will
return ``None``. The caller may wish to wrap calls to
``files()`` in `always_iterable
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
or otherwise guard against this condition if the target
distribution is not known to have the metadata present.
169

Jason R. Coombs's avatar
Jason R. Coombs committed
170
.. _requirements:
171 172 173 174 175

Distribution requirements
-------------------------

To get the full set of requirements for a distribution, use the ``requires()``
176
function::
177

178
    >>> requires('wheel')
179
    ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
180

181

Jason R. Coombs's avatar
Jason R. Coombs committed
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
Distributions
=============

While the above API is the most common and convenient usage, you can get all
of that information from the ``Distribution`` class.  A ``Distribution`` is an
abstract object that represents the metadata for a Python package.  You can
get the ``Distribution`` instance::

    >>> from importlib_metadata import distribution
    >>> dist = distribution('wheel')

Thus, an alternative way to get the version number is through the
``Distribution`` instance::

    >>> dist.version
    '0.32.3'

There are all kinds of additional metadata available on the ``Distribution``
instance::

    >>> d.metadata['Requires-Python']
    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
    >>> d.metadata['License']
    'MIT'

207 208
The full set of available metadata is not described here.  See :pep:`566`
for additional details.
Jason R. Coombs's avatar
Jason R. Coombs committed
209

210 211 212 213

Extending the search algorithm
==============================

214
Because package metadata is not available through :data:`sys.path` searches, or
215 216
package loaders directly, the metadata for a package is found through import
system `finders`_.  To find a distribution package's metadata,
217 218
``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on
:data:`sys.meta_path`.
219

220 221 222
By default ``importlib_metadata`` installs a finder for distribution packages
found on the file system.  This finder doesn't actually find any *packages*,
but it can find the packages' metadata.
223 224 225 226

The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
interface expected of finders by Python's import system.
``importlib_metadata`` extends this protocol by looking for an optional
227
``find_distributions`` callable on the finders from
228
:data:`sys.meta_path` and presents this extended interface as the
229 230
``DistributionFinder`` abstract base class, which defines this abstract
method::
231

232
    @abc.abstractmethod
233
    def find_distributions(context=DistributionFinder.Context()):
234
        """Return an iterable of all Distribution instances capable of
235
        loading the metadata for packages for the indicated ``context``.
236
        """
237

238
The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
239
properties indicating the path to search and name to match and may
240 241
supply other relevant context.

242
What this means in practice is that to support finding distribution package
243 244 245
metadata in locations other than the file system, subclass
``Distribution`` and implement the abstract methods. Then from
a custom finder, return instances of this derived ``Distribution`` in the
246
``find_distributions()`` method.
247 248 249 250 251


.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders
252 253 254 255 256


.. rubric:: Footnotes

.. [#f1] Technically, the returned distribution metadata object is an
257
         :class:`email.message.EmailMessage`
258 259 260
         instance, but this is an implementation detail, and not part of the
         stable API.  You should only use dictionary-like methods and syntax
         to access the metadata contents.