• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.. versionchanged:: 3.10
12   ``importlib.metadata`` is no longer provisional.
13
14**Source code:** :source:`Lib/importlib/metadata/__init__.py`
15
16``importlib.metadata`` is a library that provides for access to installed
17package metadata.  Built in part on Python's import system, this library
18intends to replace similar functionality in the `entry point
19API`_ and `metadata API`_ of ``pkg_resources``.  Along with
20:mod:`importlib.resources` in Python 3.7
21and newer (backported as `importlib_resources`_ for older versions of
22Python), this can eliminate the need to use the older and less efficient
23``pkg_resources`` package.
24
25By "installed package" we generally mean a third-party package installed into
26Python's ``site-packages`` directory via tools such as `pip
27<https://pypi.org/project/pip/>`_.  Specifically,
28it means a package with either a discoverable ``dist-info`` or ``egg-info``
29directory, and metadata defined by :pep:`566` or its older specifications.
30By default, package metadata can live on the file system or in zip archives on
31:data:`sys.path`.  Through an extension mechanism, the metadata can live almost
32anywhere.
33
34
35Overview
36========
37
38Let's say you wanted to get the version string for a package you've installed
39using ``pip``.  We start by creating a virtual environment and installing
40something into it:
41
42.. code-block:: shell-session
43
44    $ python3 -m venv example
45    $ source example/bin/activate
46    (example) $ pip install wheel
47
48You can get the version string for ``wheel`` by running the following:
49
50.. code-block:: pycon
51
52    (example) $ python
53    >>> from importlib.metadata import version  # doctest: +SKIP
54    >>> version('wheel')  # doctest: +SKIP
55    '0.32.3'
56
57You can also get the set of entry points keyed by group, such as
58``console_scripts``, ``distutils.commands`` and others.  Each group contains a
59sequence of :ref:`EntryPoint <entry-points>` objects.
60
61You can get the :ref:`metadata for a distribution <metadata>`::
62
63    >>> list(metadata('wheel'))  # doctest: +SKIP
64    ['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']
65
66You can also get a :ref:`distribution's version number <version>`, list its
67:ref:`constituent files <files>`, and get a list of the distribution's
68:ref:`requirements`.
69
70
71Functional API
72==============
73
74This package provides the following functionality via its public API.
75
76
77.. _entry-points:
78
79Entry points
80------------
81
82The ``entry_points()`` function returns a collection of entry points.
83Entry points are represented by ``EntryPoint`` instances;
84each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
85a ``.load()`` method to resolve the value.  There are also ``.module``,
86``.attr``, and ``.extras`` attributes for getting the components of the
87``.value`` attribute.
88
89Query all entry points::
90
91    >>> eps = entry_points()  # doctest: +SKIP
92
93The ``entry_points()`` function returns an ``EntryPoints`` object,
94a sequence of all ``EntryPoint`` objects with ``names`` and ``groups``
95attributes for convenience::
96
97    >>> sorted(eps.groups)  # doctest: +SKIP
98    ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
99
100``EntryPoints`` has a ``select`` method to select entry points
101matching specific properties. Select entry points in the
102``console_scripts`` group::
103
104    >>> scripts = eps.select(group='console_scripts')  # doctest: +SKIP
105
106Equivalently, since ``entry_points`` passes keyword arguments
107through to select::
108
109    >>> scripts = entry_points(group='console_scripts')  # doctest: +SKIP
110
111Pick out a specific script named "wheel" (found in the wheel project)::
112
113    >>> 'wheel' in scripts.names  # doctest: +SKIP
114    True
115    >>> wheel = scripts['wheel']  # doctest: +SKIP
116
117Equivalently, query for that entry point during selection::
118
119    >>> (wheel,) = entry_points(group='console_scripts', name='wheel')  # doctest: +SKIP
120    >>> (wheel,) = entry_points().select(group='console_scripts', name='wheel')  # doctest: +SKIP
121
122Inspect the resolved entry point::
123
124    >>> wheel  # doctest: +SKIP
125    EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts')
126    >>> wheel.module  # doctest: +SKIP
127    'wheel.cli'
128    >>> wheel.attr  # doctest: +SKIP
129    'main'
130    >>> wheel.extras  # doctest: +SKIP
131    []
132    >>> main = wheel.load()  # doctest: +SKIP
133    >>> main  # doctest: +SKIP
134    <function main at 0x103528488>
135
136The ``group`` and ``name`` are arbitrary values defined by the package author
137and usually a client will wish to resolve all entry points for a particular
138group.  Read `the setuptools docs
139<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
140for more information on entry points, their definition, and usage.
141
142*Compatibility Note*
143
144The "selectable" entry points were introduced in ``importlib_metadata``
1453.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted
146no parameters and always returned a dictionary of entry points, keyed
147by group. For compatibility, if no parameters are passed to entry_points,
148a ``SelectableGroups`` object is returned, implementing that dict
149interface. In the future, calling ``entry_points`` with no parameters
150will return an ``EntryPoints`` object. Users should rely on the selection
151interface to retrieve entry points by group.
152
153
154.. _metadata:
155
156Distribution metadata
157---------------------
158
159Every distribution includes some metadata, which you can extract using the
160``metadata()`` function::
161
162    >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP
163
164The keys of the returned data structure, a ``PackageMetadata``,
165name the metadata keywords, and
166the values are returned unparsed from the distribution metadata::
167
168    >>> wheel_metadata['Requires-Python']  # doctest: +SKIP
169    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
170
171``PackageMetadata`` also presents a ``json`` attribute that returns
172all the metadata in a JSON-compatible form per :PEP:`566`::
173
174    >>> wheel_metadata.json['requires_python']
175    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
176
177.. versionchanged:: 3.10
178   The ``Description`` is now included in the metadata when presented
179   through the payload. Line continuation characters have been removed.
180
181.. versionadded:: 3.10
182   The ``json`` attribute was added.
183
184
185.. _version:
186
187Distribution versions
188---------------------
189
190The ``version()`` function is the quickest way to get a distribution's version
191number, as a string::
192
193    >>> version('wheel')  # doctest: +SKIP
194    '0.32.3'
195
196
197.. _files:
198
199Distribution files
200------------------
201
202You can also get the full set of files contained within a distribution.  The
203``files()`` function takes a distribution package name and returns all of the
204files installed by this distribution.  Each file object returned is a
205``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``,
206``size``, and ``hash`` properties as indicated by the metadata.  For example::
207
208    >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]  # doctest: +SKIP
209    >>> util  # doctest: +SKIP
210    PackagePath('wheel/util.py')
211    >>> util.size  # doctest: +SKIP
212    859
213    >>> util.dist  # doctest: +SKIP
214    <importlib.metadata._hooks.PathDistribution object at 0x101e0cef0>
215    >>> util.hash  # doctest: +SKIP
216    <FileHash mode: sha256 value: bYkw5oMccfazVCoYQwKkkemoVyMAFoR34mmKBx8R1NI>
217
218Once you have the file, you can also read its contents::
219
220    >>> print(util.read_text())  # doctest: +SKIP
221    import base64
222    import sys
223    ...
224    def as_bytes(s):
225        if isinstance(s, text_type):
226            return s.encode('utf-8')
227        return s
228
229You can also use the ``locate`` method to get a the absolute path to the
230file::
231
232    >>> util.locate()  # doctest: +SKIP
233    PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
234
235In the case where the metadata file listing files
236(RECORD or SOURCES.txt) is missing, ``files()`` will
237return ``None``. The caller may wish to wrap calls to
238``files()`` in `always_iterable
239<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
240or otherwise guard against this condition if the target
241distribution is not known to have the metadata present.
242
243.. _requirements:
244
245Distribution requirements
246-------------------------
247
248To get the full set of requirements for a distribution, use the ``requires()``
249function::
250
251    >>> requires('wheel')  # doctest: +SKIP
252    ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
253
254
255Package distributions
256---------------------
257
258A convenience method to resolve the distribution or
259distributions (in the case of a namespace package) for top-level
260Python packages or modules::
261
262    >>> packages_distributions()
263    {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...}
264
265.. versionadded:: 3.10
266
267
268Distributions
269=============
270
271While the above API is the most common and convenient usage, you can get all
272of that information from the ``Distribution`` class.  A ``Distribution`` is an
273abstract object that represents the metadata for a Python package.  You can
274get the ``Distribution`` instance::
275
276    >>> from importlib.metadata import distribution  # doctest: +SKIP
277    >>> dist = distribution('wheel')  # doctest: +SKIP
278
279Thus, an alternative way to get the version number is through the
280``Distribution`` instance::
281
282    >>> dist.version  # doctest: +SKIP
283    '0.32.3'
284
285There are all kinds of additional metadata available on the ``Distribution``
286instance::
287
288    >>> dist.metadata['Requires-Python']  # doctest: +SKIP
289    '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
290    >>> dist.metadata['License']  # doctest: +SKIP
291    'MIT'
292
293The full set of available metadata is not described here.  See :pep:`566`
294for additional details.
295
296
297Extending the search algorithm
298==============================
299
300Because package metadata is not available through :data:`sys.path` searches, or
301package loaders directly, the metadata for a package is found through import
302system :ref:`finders <finders-and-loaders>`.  To find a distribution package's metadata,
303``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on
304:data:`sys.meta_path`.
305
306The default ``PathFinder`` for Python includes a hook that calls into
307``importlib.metadata.MetadataPathFinder`` for finding distributions
308loaded from typical file-system-based paths.
309
310The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
311interface expected of finders by Python's import system.
312``importlib.metadata`` extends this protocol by looking for an optional
313``find_distributions`` callable on the finders from
314:data:`sys.meta_path` and presents this extended interface as the
315``DistributionFinder`` abstract base class, which defines this abstract
316method::
317
318    @abc.abstractmethod
319    def find_distributions(context=DistributionFinder.Context()):
320        """Return an iterable of all Distribution instances capable of
321        loading the metadata for packages for the indicated ``context``.
322        """
323
324The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
325properties indicating the path to search and name to match and may
326supply other relevant context.
327
328What this means in practice is that to support finding distribution package
329metadata in locations other than the file system, subclass
330``Distribution`` and implement the abstract methods. Then from
331a custom finder, return instances of this derived ``Distribution`` in the
332``find_distributions()`` method.
333
334
335.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
336.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
337.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
338
339
340.. rubric:: Footnotes
341