bpo-34632: Add importlib.metadata (GH-12547) · python/cpython@1bbf7b6 (original) (raw)

``

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

``

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


``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


``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

`+

.. highlight:: none

`

``

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

`+

.. highlight:: none

`

``

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.

``

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

+

``

93


The ``group`` and ``name`` are arbitrary values defined by the package author

``

94

`+

and usually a client will wish to resolve all entry points for a particular

`

``

95

`` +

group. Read `the setuptools docs

``

``

96

`` +

https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins`_

``

``

97

`+

for more information on entrypoints, their definition, and usage.

`

``

98

+

``

99

+

``

100

`+

.. _metadata:

`

``

101

+

``

102

`+

Distribution metadata

`

``

103

`+


`

``

104

+

``

105

`+

Every distribution includes some metadata, which you can extract using the

`

``

106


``metadata()`` function::

``

107

+

``

108

`+

wheel_metadata = metadata('wheel') # doctest: +SKIP

`

``

109

+

``

110

`+

The keys of the returned data structure [#f1]_ name the metadata keywords, and

`

``

111

`+

their 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

+

``

119

`+

Distribution versions

`

``

120

`+


`

``

121

+

``

122


The ``version()`` function is the quickest way to get a distribution's version

``

123

`+

number, as a string::

`

``

124

+

``

125

`+

version('wheel') # doctest: +SKIP

`

``

126

`+

'0.32.3'

`

``

127

+

``

128

+

``

129

`+

.. _files:

`

``

130

+

``

131

`+

Distribution files

`

``

132

`+


`

``

133

+

``

134

`+

You 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

``

136

`+

files 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

`+

`

``

149

+

``

150

`+

Once 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

+

``

161

+

``

162

`+

.. _requirements:

`

``

163

+

``

164

`+

Distribution requirements

`

``

165

`+


`

``

166

+

``

167


To get the full set of requirements for a distribution, use the ``requires()``

``

168

`+

function. Note that this returns an iterator::

`

``

169

+

``

170

`+

list(requires('wheel')) # doctest: +SKIP

`

``

171

`+

["pytest (>=3.0.0) ; extra == 'test'"]

`

``

172

+

``

173

+

``

174

`+

Distributions

`

``

175

`+

=============

`

``

176

+

``

177

`+

While the above API is the most common and convenient usage, you can get all

`

``

178


of that information from the ``Distribution`` class. A ``Distribution`` is an

``

179

`+

abstract object that represents the metadata for a Python package. You can

`

``

180


get the ``Distribution`` instance::

``

181

+

``

182

`+

from importlib.metadata import distribution # doctest: +SKIP

`

``

183

`+

dist = distribution('wheel') # doctest: +SKIP

`

``

184

+

``

185

`+

Thus, an alternative way to get the version number is through the

`

``

186


``Distribution`` instance::

``

187

+

``

188

`+

dist.version # doctest: +SKIP

`

``

189

`+

'0.32.3'

`

``

190

+

``

191


There are all kinds of additional metadata available on the ``Distribution``

``

192

`+

instance::

`

``

193

+

``

194

`+

d.metadata['Requires-Python'] # doctest: +SKIP

`

``

195

`+

'>=2.7, !=3.0., !=3.1., !=3.2., !=3.3.'

`

``

196

`+

d.metadata['License'] # doctest: +SKIP

`

``

197

`+

'MIT'

`

``

198

+

``

199

`` +

The full set of available metadata is not described here. See `PEP 566

``

``

200

`` +

https://www.python.org/dev/peps/pep-0566/`_ for additional details.

``

``

201

+

``

202

+

``

203

`+

Extending the search algorithm

`

``

204

`+

==============================

`

``

205

+

``

206


Because package metadata is not available through ``sys.path`` searches, or

``

207

`+

package loaders directly, the metadata for a package is found through import

`

``

208

`` +

system finders_. To find a distribution package's metadata,

``

``

209


``importlib.metadata`` queries the list of `meta path finders`_ on

``

210

`` +

sys.meta_path_.

``

``

211

+

``

212


By default ``importlib.metadata`` installs a finder for distribution packages

``

213

`+

found on the file system. This finder doesn't actually find any packages,

`

``

214

`+

but it can find the packages' metadata.

`

``

215

+

``

216

`` +

The abstract class :py:class:importlib.abc.MetaPathFinder defines the

``

``

217

`+

interface expected of finders by Python's import system.

`

``

218


``importlib.metadata`` extends this protocol by looking for an optional

``

219


``find_distributions`` callable on the finders from

``

220


``sys.meta_path``. If the finder has this method, it must return

``

221


an iterator over instances of the ``Distribution`` abstract class. This

``

222

`+

method must have the signature::

`

``

223

+

``

224

`+

def find_distributions(name=None, path=None):

`

``

225

`+

"""Return an iterable of all Distribution instances capable of

`

``

226

`+

loading the metadata for packages matching the name

`

``

227

`+

(or all names if not supplied) along the paths in the list

`

``

228


 of directories ``path`` (defaults to sys.path).

``

229

`+

"""

`

``

230

+

``

231

`+

What this means in practice is that to support finding distribution package

`

``

232

`+

metadata in locations other than the file system, you should derive from

`

``

233


``Distribution`` and implement the ``load_metadata()`` method. This takes a

``

234

`+

single argument which is the name of the package whose metadata is being

`

``

235


found. This instance of the ``Distribution`` base abstract class is what your

``

236


finder's ``find_distributions()`` method should return.

``

237

+

``

238

+

``

239

`` +

.. _entry point API: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points

``

``

240

`` +

.. _metadata API: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api

``

``

241

`` +

.. _Python 3.7 and newer: https://docs.python.org/3/library/importlib.html#module-importlib.resources

``

``

242

`` +

.. _importlib_resources: https://importlib-resources.readthedocs.io/en/latest/index.html

``

``

243

`` +

.. _PEP 566: https://www.python.org/dev/peps/pep-0566/

``

``

244

`` +

.. _finders: https://docs.python.org/3/reference/import.html#finders-and-loaders

``

``

245

`` +

.. _meta path finders: https://docs.python.org/3/glossary.html#term-meta-path-finder

``

``

246

`` +

.. _sys.meta_path: https://docs.python.org/3/library/sys.html#sys.meta_path

``

``

247

`` +

.. _pathlib.Path: https://docs.python.org/3/library/pathlib.html#pathlib.Path

``

``

248

+

``

249

+

``

250

`+

.. rubric:: Footnotes

`

``

251

+

``

252

`+

.. [#f1] Technically, the returned distribution metadata object is an

`

``

253

`` +

`email.message.Message

``

``

254

`` +

https://docs.python.org/3/library/email.message.html#email.message.EmailMessage`_

``

``

255

`+

instance, but this is an implementation detail, and not part of the

`

``

256

`+

stable API. You should only use dictionary-like methods and syntax

`

``

257

`+

to access the metadata contents.

`