• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _using:
2
3==========================
4 Using 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``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``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.
81
82    >>> eps = entry_points()  # doctest: +SKIP
83    >>> list(eps)  # doctest: +SKIP
84    ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
85    >>> scripts = eps['console_scripts']  # doctest: +SKIP
86    >>> wheel = [ep for ep in scripts if ep.name == 'wheel'][0]  # doctest: +SKIP
87    >>> wheel  # doctest: +SKIP
88    EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
89    >>> main = wheel.load()  # doctest: +SKIP
90    >>> main  # doctest: +SKIP
91    <function main at 0x103528488>
92
93The ``group`` and ``name`` are arbitrary values defined by the package author
94and usually a client will wish to resolve all entry points for a particular
95group.  Read `the setuptools docs
96<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
97for more information on entrypoints, their definition, and usage.
98
99
100.. _metadata:
101
102Distribution metadata
103---------------------
104
105Every distribution includes some metadata, which you can extract using the
106``metadata()`` function::
107
108    >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP
109
110The keys of the returned data structure [#f1]_ name the metadata keywords, and
111their values are returned unparsed from the distribution metadata::
112
113    >>> wheel_metadata['Requires-Python']  # doctest: +SKIP
114    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
115
116
117.. _version:
118
119Distribution versions
120---------------------
121
122The ``version()`` function is the quickest way to get a distribution's version
123number, as a string::
124
125    >>> version('wheel')  # doctest: +SKIP
126    '0.32.3'
127
128
129.. _files:
130
131Distribution files
132------------------
133
134You can also get the full set of files contained within a distribution.  The
135``files()`` function takes a distribution package name and returns all of the
136files installed by this distribution.  Each file object returned is a
137``PackagePath``, a `pathlib.Path`_ derived object with additional ``dist``,
138``size``, and ``hash`` properties as indicated by the metadata.  For example::
139
140    >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]  # doctest: +SKIP
141    >>> util  # doctest: +SKIP
142    PackagePath('wheel/util.py')
143    >>> util.size  # doctest: +SKIP
144    859
145    >>> util.dist  # doctest: +SKIP
146    <importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
147    >>> util.hash  # doctest: +SKIP
148    <FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>
149
150Once you have the file, you can also read its contents::
151
152    >>> print(util.read_text())  # doctest: +SKIP
153    import base64
154    import sys
155    ...
156    def as_bytes(s):
157        if isinstance(s, text_type):
158            return s.encode('utf-8')
159        return s
160
161In the case where the metadata file listing files
162(RECORD or SOURCES.txt) is missing, ``files()`` will
163return ``None``. The caller may wish to wrap calls to
164``files()`` in `always_iterable
165<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
166or otherwise guard against this condition if the target
167distribution is not known to have the metadata present.
168
169.. _requirements:
170
171Distribution requirements
172-------------------------
173
174To get the full set of requirements for a distribution, use the ``requires()``
175function::
176
177    >>> requires('wheel')  # doctest: +SKIP
178    ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
179
180
181Distributions
182=============
183
184While the above API is the most common and convenient usage, you can get all
185of that information from the ``Distribution`` class.  A ``Distribution`` is an
186abstract object that represents the metadata for a Python package.  You can
187get the ``Distribution`` instance::
188
189    >>> from importlib.metadata import distribution  # doctest: +SKIP
190    >>> dist = distribution('wheel')  # doctest: +SKIP
191
192Thus, an alternative way to get the version number is through the
193``Distribution`` instance::
194
195    >>> dist.version  # doctest: +SKIP
196    '0.32.3'
197
198There are all kinds of additional metadata available on the ``Distribution``
199instance::
200
201    >>> d.metadata['Requires-Python']  # doctest: +SKIP
202    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
203    >>> d.metadata['License']  # doctest: +SKIP
204    'MIT'
205
206The full set of available metadata is not described here.  See `PEP 566
207<https://www.python.org/dev/peps/pep-0566/>`_ for additional details.
208
209
210Extending the search algorithm
211==============================
212
213Because package metadata is not available through ``sys.path`` searches, or
214package loaders directly, the metadata for a package is found through import
215system `finders`_.  To find a distribution package's metadata,
216``importlib.metadata`` queries the list of `meta path finders`_ on
217`sys.meta_path`_.
218
219The default ``PathFinder`` for Python includes a hook that calls into
220``importlib.metadata.MetadataPathFinder`` for finding distributions
221loaded from typical file-system-based paths.
222
223The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
224interface expected of finders by Python's import system.
225``importlib.metadata`` extends this protocol by looking for an optional
226``find_distributions`` callable on the finders from
227``sys.meta_path`` and presents this extended interface as the
228``DistributionFinder`` abstract base class, which defines this abstract
229method::
230
231    @abc.abstractmethod
232    def find_distributions(context=DistributionFinder.Context()):
233        """Return an iterable of all Distribution instances capable of
234        loading the metadata for packages for the indicated ``context``.
235        """
236
237The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
238properties indicating the path to search and names to match and may
239supply other relevant context.
240
241What this means in practice is that to support finding distribution package
242metadata in locations other than the file system, subclass
243``Distribution`` and implement the abstract methods. Then from
244a custom finder, return instances of this derived ``Distribution`` in the
245``find_distributions()`` method.
246
247
248.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
249.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
250.. _`Python 3.7 and newer`: https://docs.python.org/3/library/importlib.html#module-importlib.resources
251.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
252.. _`PEP 566`: https://www.python.org/dev/peps/pep-0566/
253.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders
254.. _`meta path finders`: https://docs.python.org/3/glossary.html#term-meta-path-finder
255.. _`sys.meta_path`: https://docs.python.org/3/library/sys.html#sys.meta_path
256.. _`pathlib.Path`: https://docs.python.org/3/library/pathlib.html#pathlib.Path
257
258
259.. rubric:: Footnotes
260
261.. [#f1] Technically, the returned distribution metadata object is an
262         `email.message.Message
263         <https://docs.python.org/3/library/email.message.html#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