1.. _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 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. 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 161In the case where the metadata file listing files 162(RECORD or SOURCES.txt) is missing, ``files()`` will 163return ``None``. The caller may wish to wrap calls to 164``files()`` in `always_iterable 165<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_ 166or otherwise guard against this condition if the target 167distribution is not known to have the metadata present. 168 169.. _requirements: 170 171Distribution requirements 172------------------------- 173 174To get the full set of requirements for a distribution, use the ``requires()`` 175function:: 176 177 >>> requires('wheel') # doctest: +SKIP 178 ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] 179 180 181Distributions 182============= 183 184While the above API is the most common and convenient usage, you can get all 185of that information from the ``Distribution`` class. A ``Distribution`` is an 186abstract object that represents the metadata for a Python package. You can 187get the ``Distribution`` instance:: 188 189 >>> from importlib.metadata import distribution # doctest: +SKIP 190 >>> dist = distribution('wheel') # doctest: +SKIP 191 192Thus, an alternative way to get the version number is through the 193``Distribution`` instance:: 194 195 >>> dist.version # doctest: +SKIP 196 '0.32.3' 197 198There are all kinds of additional metadata available on the ``Distribution`` 199instance:: 200 201 >>> d.metadata['Requires-Python'] # doctest: +SKIP 202 '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' 203 >>> d.metadata['License'] # doctest: +SKIP 204 'MIT' 205 206The full set of available metadata is not described here. See `PEP 566 207<https://www.python.org/dev/peps/pep-0566/>`_ for additional details. 208 209 210Extending the search algorithm 211============================== 212 213Because package metadata is not available through ``sys.path`` searches, or 214package loaders directly, the metadata for a package is found through import 215system `finders`_. To find a distribution package's metadata, 216``importlib.metadata`` queries the list of `meta path finders`_ on 217`sys.meta_path`_. 218 219The default ``PathFinder`` for Python includes a hook that calls into 220``importlib.metadata.MetadataPathFinder`` for finding distributions 221loaded from typical file-system-based paths. 222 223The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the 224interface expected of finders by Python's import system. 225``importlib.metadata`` extends this protocol by looking for an optional 226``find_distributions`` callable on the finders from 227``sys.meta_path`` and presents this extended interface as the 228``DistributionFinder`` abstract base class, which defines this abstract 229method:: 230 231 @abc.abstractmethod 232 def find_distributions(context=DistributionFinder.Context()): 233 """Return an iterable of all Distribution instances capable of 234 loading the metadata for packages for the indicated ``context``. 235 """ 236 237The ``DistributionFinder.Context`` object provides ``.path`` and ``.name`` 238properties indicating the path to search and names to match and may 239supply other relevant context. 240 241What this means in practice is that to support finding distribution package 242metadata in locations other than the file system, subclass 243``Distribution`` and implement the abstract methods. Then from 244a custom finder, return instances of this derived ``Distribution`` in the 245``find_distributions()`` method. 246 247 248.. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points 249.. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api 250.. _`Python 3.7 and newer`: https://docs.python.org/3/library/importlib.html#module-importlib.resources 251.. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html 252.. _`PEP 566`: https://www.python.org/dev/peps/pep-0566/ 253.. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders 254.. _`meta path finders`: https://docs.python.org/3/glossary.html#term-meta-path-finder 255.. _`sys.meta_path`: https://docs.python.org/3/library/sys.html#sys.meta_path 256.. _`pathlib.Path`: https://docs.python.org/3/library/pathlib.html#pathlib.Path 257 258 259.. rubric:: Footnotes 260 261.. [#f1] Technically, the returned distribution metadata object is an 262 `email.message.Message 263 <https://docs.python.org/3/library/email.message.html#email.message.EmailMessage>`_ 264 instance, but this is an implementation detail, and not part of the 265 stable API. You should only use dictionary-like methods and syntax 266 to access the metadata contents. 267