• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
12 package metadata.  Built in part on Python's import system, this library
13 intends to replace similar functionality in the `entry point
14 API`_ and `metadata API`_ of ``pkg_resources``.  Along with
15 :mod:`importlib.resources` in Python 3.7
16 and newer (backported as `importlib_resources`_ for older versions of
17 Python), this can eliminate the need to use the older and less efficient
18 ``pkg_resources`` package.
19 
20 By "installed package" we generally mean a third-party package installed into
21 Python's ``site-packages`` directory via tools such as `pip
22 <https://pypi.org/project/pip/>`_.  Specifically,
23 it means a package with either a discoverable ``dist-info`` or ``egg-info``
24 directory, and metadata defined by :pep:`566` or its older specifications.
25 By 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
27 anywhere.
28 
29 
30 Overview
31 ========
32 
33 Let's say you wanted to get the version string for a package you've installed
34 using ``pip``.  We start by creating a virtual environment and installing
35 something 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 
43 You 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 
52 You can also get the set of entry points keyed by group, such as
53 ``console_scripts``, ``distutils.commands`` and others.  Each group contains a
54 sequence of :ref:`EntryPoint <entry-points>` objects.
55 
56 You 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 
61 You 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 
66 Functional API
67 ==============
68 
69 This package provides the following functionality via its public API.
70 
71 
72 .. _entry-points:
73 
74 Entry points
75 ------------
76 
77 The ``entry_points()`` function returns a dictionary of all entry points,
78 keyed by group.  Entry points are represented by ``EntryPoint`` instances;
79 each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
80 a ``.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 
101 The ``group`` and ``name`` are arbitrary values defined by the package author
102 and usually a client will wish to resolve all entry points for a particular
103 group.  Read `the setuptools docs
104 <https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
105 for more information on entry points, their definition, and usage.
106 
107 
108 .. _metadata:
109 
110 Distribution metadata
111 ---------------------
112 
113 Every distribution includes some metadata, which you can extract using the
114 ``metadata()`` function::
115 
116     >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP
117 
118 The keys of the returned data structure [#f1]_ name the metadata keywords, and
119 their 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 
127 Distribution versions
128 ---------------------
129 
130 The ``version()`` function is the quickest way to get a distribution's version
131 number, as a string::
132 
133     >>> version('wheel')  # doctest: +SKIP
134     '0.32.3'
135 
136 
137 .. _files:
138 
139 Distribution files
140 ------------------
141 
142 You 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
144 files 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 
158 Once 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 
169 In the case where the metadata file listing files
170 (RECORD or SOURCES.txt) is missing, ``files()`` will
171 return ``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>`_
174 or otherwise guard against this condition if the target
175 distribution is not known to have the metadata present.
176 
177 .. _requirements:
178 
179 Distribution requirements
180 -------------------------
181 
182 To get the full set of requirements for a distribution, use the ``requires()``
183 function::
184 
185     >>> requires('wheel')  # doctest: +SKIP
186     ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
187 
188 
189 Distributions
190 =============
191 
192 While the above API is the most common and convenient usage, you can get all
193 of that information from the ``Distribution`` class.  A ``Distribution`` is an
194 abstract object that represents the metadata for a Python package.  You can
195 get the ``Distribution`` instance::
196 
197     >>> from importlib.metadata import distribution  # doctest: +SKIP
198     >>> dist = distribution('wheel')  # doctest: +SKIP
199 
200 Thus, 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 
206 There are all kinds of additional metadata available on the ``Distribution``
207 instance::
208 
209     >>> dist.metadata['Requires-Python']  # doctest: +SKIP
210     '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
211     >>> dist.metadata['License']  # doctest: +SKIP
212     'MIT'
213 
214 The full set of available metadata is not described here.  See :pep:`566`
215 for additional details.
216 
217 
218 Extending the search algorithm
219 ==============================
220 
221 Because package metadata is not available through :data:`sys.path` searches, or
222 package loaders directly, the metadata for a package is found through import
223 system :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 
227 The default ``PathFinder`` for Python includes a hook that calls into
228 ``importlib.metadata.MetadataPathFinder`` for finding distributions
229 loaded from typical file-system-based paths.
230 
231 The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the
232 interface 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
237 method::
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 
245 The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
246 properties indicating the path to search and name to match and may
247 supply other relevant context.
248 
249 What this means in practice is that to support finding distribution package
250 metadata in locations other than the file system, subclass
251 ``Distribution`` and implement the abstract methods. Then from
252 a 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