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