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 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:mod:`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:data:`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. 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 101The ``group`` and ``name`` are arbitrary values defined by the package author 102and usually a client will wish to resolve all entry points for a particular 103group. Read `the setuptools docs 104<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_ 105for more information on entry points, their definition, and usage. 106 107 108.. _metadata: 109 110Distribution metadata 111--------------------- 112 113Every distribution includes some metadata, which you can extract using the 114``metadata()`` function:: 115 116 >>> wheel_metadata = metadata('wheel') # doctest: +SKIP 117 118The keys of the returned data structure [#f1]_ name the metadata keywords, and 119their 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 127Distribution versions 128--------------------- 129 130The ``version()`` function is the quickest way to get a distribution's version 131number, as a string:: 132 133 >>> version('wheel') # doctest: +SKIP 134 '0.32.3' 135 136 137.. _files: 138 139Distribution files 140------------------ 141 142You 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 144files 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 158Once 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 169In the case where the metadata file listing files 170(RECORD or SOURCES.txt) is missing, ``files()`` will 171return ``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>`_ 174or otherwise guard against this condition if the target 175distribution is not known to have the metadata present. 176 177.. _requirements: 178 179Distribution requirements 180------------------------- 181 182To get the full set of requirements for a distribution, use the ``requires()`` 183function:: 184 185 >>> requires('wheel') # doctest: +SKIP 186 ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] 187 188 189Distributions 190============= 191 192While the above API is the most common and convenient usage, you can get all 193of that information from the ``Distribution`` class. A ``Distribution`` is an 194abstract object that represents the metadata for a Python package. You can 195get the ``Distribution`` instance:: 196 197 >>> from importlib.metadata import distribution # doctest: +SKIP 198 >>> dist = distribution('wheel') # doctest: +SKIP 199 200Thus, 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 206There are all kinds of additional metadata available on the ``Distribution`` 207instance:: 208 209 >>> d.metadata['Requires-Python'] # doctest: +SKIP 210 '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' 211 >>> d.metadata['License'] # doctest: +SKIP 212 'MIT' 213 214The full set of available metadata is not described here. See :pep:`566` 215for additional details. 216 217 218Extending the search algorithm 219============================== 220 221Because package metadata is not available through :data:`sys.path` searches, or 222package loaders directly, the metadata for a package is found through import 223system :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 227The default ``PathFinder`` for Python includes a hook that calls into 228``importlib.metadata.MetadataPathFinder`` for finding distributions 229loaded from typical file-system-based paths. 230 231The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the 232interface 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 237method:: 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 245The ``DistributionFinder.Context`` object provides ``.path`` and ``.name`` 246properties indicating the path to search and name to match and may 247supply other relevant context. 248 249What this means in practice is that to support finding distribution package 250metadata in locations other than the file system, subclass 251``Distribution`` and implement the abstract methods. Then from 252a 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