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