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