blob: 2126498e728cfa4667ea8a470a0040df3708d8b5 [file] [log] [blame]
Jason R. Coombs1bbf7b62019-05-24 19:59:01 -04001.. _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
Anthony Sottile22ccb0b2019-05-26 07:30:52 -070035something into it:
Jason R. Coombs1bbf7b62019-05-24 19:59:01 -040036
Anthony Sottile22ccb0b2019-05-26 07:30:52 -070037.. code-block:: shell-session
Jason R. Coombs1bbf7b62019-05-24 19:59:01 -040038
39 $ python3 -m venv example
40 $ source example/bin/activate
41 (example) $ pip install wheel
42
Anthony Sottile22ccb0b2019-05-26 07:30:52 -070043You can get the version string for ``wheel`` by running the following:
Jason R. Coombs1bbf7b62019-05-24 19:59:01 -040044
Anthony Sottile22ccb0b2019-05-26 07:30:52 -070045.. code-block:: pycon
Jason R. Coombs1bbf7b62019-05-24 19:59:01 -040046
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
161
162.. _requirements:
163
164Distribution requirements
165-------------------------
166
167To get the full set of requirements for a distribution, use the ``requires()``
168function. Note that this returns an iterator::
169
170 >>> list(requires('wheel')) # doctest: +SKIP
171 ["pytest (>=3.0.0) ; extra == 'test'"]
172
173
174Distributions
175=============
176
177While the above API is the most common and convenient usage, you can get all
178of that information from the ``Distribution`` class. A ``Distribution`` is an
179abstract object that represents the metadata for a Python package. You can
180get the ``Distribution`` instance::
181
182 >>> from importlib.metadata import distribution # doctest: +SKIP
183 >>> dist = distribution('wheel') # doctest: +SKIP
184
185Thus, an alternative way to get the version number is through the
186``Distribution`` instance::
187
188 >>> dist.version # doctest: +SKIP
189 '0.32.3'
190
191There are all kinds of additional metadata available on the ``Distribution``
192instance::
193
194 >>> d.metadata['Requires-Python'] # doctest: +SKIP
195 '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
196 >>> d.metadata['License'] # doctest: +SKIP
197 'MIT'
198
199The full set of available metadata is not described here. See `PEP 566
200<https://www.python.org/dev/peps/pep-0566/>`_ for additional details.
201
202
203Extending the search algorithm
204==============================
205
206Because package metadata is not available through ``sys.path`` searches, or
207package loaders directly, the metadata for a package is found through import
208system `finders`_. To find a distribution package's metadata,
209``importlib.metadata`` queries the list of `meta path finders`_ on
210`sys.meta_path`_.
211
212By default ``importlib.metadata`` installs a finder for distribution packages
213found on the file system. This finder doesn't actually find any *packages*,
214but it can find the packages' metadata.
215
216The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
217interface expected of finders by Python's import system.
218``importlib.metadata`` extends this protocol by looking for an optional
219``find_distributions`` callable on the finders from
220``sys.meta_path``. If the finder has this method, it must return
221an iterator over instances of the ``Distribution`` abstract class. This
222method must have the signature::
223
224 def find_distributions(name=None, path=None):
225 """Return an iterable of all Distribution instances capable of
226 loading the metadata for packages matching the name
227 (or all names if not supplied) along the paths in the list
228 of directories ``path`` (defaults to sys.path).
229 """
230
231What this means in practice is that to support finding distribution package
232metadata in locations other than the file system, you should derive from
233``Distribution`` and implement the ``load_metadata()`` method. This takes a
234single argument which is the name of the package whose metadata is being
235found. This instance of the ``Distribution`` base abstract class is what your
236finder's ``find_distributions()`` method should return.
237
238
239.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
240.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api
241.. _`Python 3.7 and newer`: https://docs.python.org/3/library/importlib.html#module-importlib.resources
242.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html
243.. _`PEP 566`: https://www.python.org/dev/peps/pep-0566/
244.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders
245.. _`meta path finders`: https://docs.python.org/3/glossary.html#term-meta-path-finder
246.. _`sys.meta_path`: https://docs.python.org/3/library/sys.html#sys.meta_path
247.. _`pathlib.Path`: https://docs.python.org/3/library/pathlib.html#pathlib.Path
248
249
250.. rubric:: Footnotes
251
252.. [#f1] Technically, the returned distribution metadata object is an
253 `email.message.Message
254 <https://docs.python.org/3/library/email.message.html#email.message.EmailMessage>`_
255 instance, but this is an implementation detail, and not part of the
256 stable API. You should only use dictionary-like methods and syntax
257 to access the metadata contents.