1.. _using:
2
3=================================
4 Using :mod:`!importlib.metadata`
5=================================
6
7.. note::
8   This functionality is provisional and may deviate from the usual
9   version semantics of the standard library.
10
11``importlib.metadata`` is a library that provides for access to installed
12package metadata.  Built in part on Python's import system, this library
13intends to replace similar functionality in the `entry point
14API`_ and `metadata API`_ of ``pkg_resources``.  Along with
15:mod:`importlib.resources` in Python 3.7
16and newer (backported as `importlib_resources`_ for older versions of
17Python), this can eliminate the need to use the older and less efficient
18``pkg_resources`` package.
19
20By "installed package" we generally mean a third-party package installed into
21Python's ``site-packages`` directory via tools such as `pip
22<https://pypi.org/project/pip/>`_.  Specifically,
23it means a package with either a discoverable ``dist-info`` or ``egg-info``
24directory, and metadata defined by :pep:`566` or its older specifications.
25By default, package metadata can live on the file system or in zip archives on
26:data:`sys.path`.  Through an extension mechanism, the metadata can live almost
27anywhere.
28
29
30Overview
31========
32
33Let's say you wanted to get the version string for a package you've installed
34using ``pip``.  We start by creating a virtual environment and installing
35something into it:
36
37.. code-block:: shell-session
38
39    $ python3 -m venv example
40    $ source example/bin/activate
41    (example) $ pip install wheel
42
43You can get the version string for ``wheel`` by running the following:
44
45.. code-block:: pycon
46
47    (example) $ python
48    >>> from importlib.metadata import version  # doctest: +SKIP
49    >>> version('wheel')  # doctest: +SKIP
50    '0.32.3'
51
52You can also get the set of entry points keyed by group, such as
53``console_scripts``, ``distutils.commands`` and others.  Each group contains a
54sequence of :ref:`EntryPoint <entry-points>` objects.
55
56You can get the :ref:`metadata for a distribution <metadata>`::
57
58    >>> list(metadata('wheel'))  # doctest: +SKIP
59    ['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']
60
61You can also get a :ref:`distribution's version number <version>`, list its
62:ref:`constituent files <files>`, and get a list of the distribution's
63:ref:`requirements`.
64
65
66Functional API
67==============
68
69This package provides the following functionality via its public API.
70
71
72.. _entry-points:
73
74Entry points
75------------
76
77The ``entry_points()`` function returns a dictionary of all entry points,
78keyed by group.  Entry points are represented by ``EntryPoint`` instances;
79each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
80a ``.load()`` method to resolve the value.  There are also ``.module``,
81``.attr``, and ``.extras`` attributes for getting the components of the
82``.value`` attribute::
83
84    >>> eps = entry_points()  # doctest: +SKIP
85    >>> list(eps)  # doctest: +SKIP
86    ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
87    >>> scripts = eps['console_scripts']  # doctest: +SKIP
88    >>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0]  # doctest: +SKIP
89    >>> wheel  # doctest: +SKIP
90    EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
91    >>> wheel.module  # doctest: +SKIP
92    'wheel.cli'
93    >>> wheel.attr  # doctest: +SKIP
94    'main'
95    >>> wheel.extras  # doctest: +SKIP
96    []
97    >>> main = wheel.load()  # doctest: +SKIP
98    >>> main  # doctest: +SKIP
99    <function main at 0x103528488>
100
101The ``group`` and ``name`` are arbitrary values defined by the package author
102and usually a client will wish to resolve all entry points for a particular
103group.  Read `the setuptools docs
104<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
105for more information on entry points, their definition, and usage.
106
107
108.. _metadata:
109
110Distribution metadata
111---------------------
112
113Every distribution includes some metadata, which you can extract using the
114``metadata()`` function::
115
116    >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP
117
118The keys of the returned data structure [#f1]_ name the metadata keywords, and
119their values are returned unparsed from the distribution metadata::
120
121    >>> wheel_metadata['Requires-Python']  # doctest: +SKIP
122    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
123
124
125.. _version:
126
127Distribution versions
128---------------------
129
130The ``version()`` function is the quickest way to get a distribution's version
131number, as a string::
132
133    >>> version('wheel')  # doctest: +SKIP
134    '0.32.3'
135
136
137.. _files:
138
139Distribution files
140------------------
141
142You can also get the full set of files contained within a distribution.  The
143``files()`` function takes a distribution package name and returns all of the
144files installed by this distribution.  Each file object returned is a
145``PackagePath``, a :class:`pathlib.Path` derived object with additional ``dist``,
146``size``, and ``hash`` properties as indicated by the metadata.  For example::
147
148    >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]  # doctest: +SKIP
149    >>> util  # doctest: +SKIP
150    PackagePath('wheel/util.py')
151    >>> util.size  # doctest: +SKIP
152    859
153    >>> util.dist  # doctest: +SKIP
154    <importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
155    >>> util.hash  # doctest: +SKIP
156    <FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>
157
158Once you have the file, you can also read its contents::
159
160    >>> print(util.read_text())  # doctest: +SKIP
161    import base64
162    import sys
163    ...
164    def as_bytes(s):
165        if isinstance(s, text_type):
166            return s.encode('utf-8')
167        return s
168
169In the case where the metadata file listing files
170(RECORD or SOURCES.txt) is missing, ``files()`` will
171return ``None``. The caller may wish to wrap calls to
172``files()`` in `always_iterable
173<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
174or otherwise guard against this condition if the target
175distribution is not known to have the metadata present.
176
177.. _requirements:
178
179Distribution requirements
180-------------------------
181
182To get the full set of requirements for a distribution, use the ``requires()``
183function::
184
185    >>> requires('wheel')  # doctest: +SKIP
186    ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
187
188
189Distributions
190=============
191
192While the above API is the most common and convenient usage, you can get all
193of that information from the ``Distribution`` class.  A ``Distribution`` is an
194abstract object that represents the metadata for a Python package.  You can
195get the ``Distribution`` instance::
196
197    >>> from importlib.metadata import distribution  # doctest: +SKIP
198    >>> dist = distribution('wheel')  # doctest: +SKIP
199
200Thus, an alternative way to get the version number is through the
201``Distribution`` instance::
202
203    >>> dist.version  # doctest: +SKIP
204    '0.32.3'
205
206There are all kinds of additional metadata available on the ``Distribution``
207instance::
208
209    >>> d.metadata['Requires-Python']  # doctest: +SKIP
210    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
211    >>> d.metadata['License']  # doctest: +SKIP
212    'MIT'
213
214The full set of available metadata is not described here.  See :pep:`566`
215for additional details.
216
217
218Extending the search algorithm
219==============================
220
221Because package metadata is not available through :data:`sys.path` searches, or
222package loaders directly, the metadata for a package is found through import
223system :ref:`finders <finders-and-loaders>`.  To find a distribution package's metadata,
224``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on
225:data:`sys.meta_path`.
226
227The default ``PathFinder`` for Python includes a hook that calls into
228``importlib.metadata.MetadataPathFinder`` for finding distributions
229loaded from typical file-system-based paths.
230
231The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
232interface expected of finders by Python's import system.
233``importlib.metadata`` extends this protocol by looking for an optional
234``find_distributions`` callable on the finders from
235:data:`sys.meta_path` and presents this extended interface as the
236``DistributionFinder`` abstract base class, which defines this abstract
237method::
238
239    @abc.abstractmethod
240    def find_distributions(context=DistributionFinder.Context()):
241        """Return an iterable of all Distribution instances capable of
242        loading the metadata for packages for the indicated ``context``.
243        """
244
245The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
246properties indicating the path to search and name to match and may
247supply other relevant context.
248
249What this means in practice is that to support finding distribution package
250metadata in locations other than the file system, subclass
251``Distribution`` and implement the abstract methods. Then from
252a custom finder, return instances of this derived ``Distribution`` in the
253``find_distributions()`` method.
254
255
256.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
257.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
258.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
259
260
261.. rubric:: Footnotes
262
263.. [#f1] Technically, the returned distribution metadata object is an
264         :class:`email.message.EmailMessage`
265         instance, but this is an implementation detail, and not part of the
266         stable API.  You should only use dictionary-like methods and syntax
267         to access the metadata contents.
268