Skip to content

Commit fa32f00

Browse files
ncoghlanwebknjazAA-Turner
authored
[3.12] gh-125018: Add importlib.metadata semantic link targets (GH-125027) (#125048)
This allows direct intersphinx references to APIs via references like `` :func:`importlib.metadata.version` ``. --------- (cherry picked from commit cda3b5a) Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]> Co-authored-by: Adam Turner <[email protected]>
1 parent 36f9bbb commit fa32f00

File tree

2 files changed

+148
-44
lines changed

2 files changed

+148
-44
lines changed

Doc/library/importlib.metadata.rst

Lines changed: 144 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ You can also get a :ref:`distribution's version number <version>`, list its
100100
:ref:`requirements`.
101101

102102

103+
.. exception:: PackageNotFoundError
104+
105+
Subclass of :class:`ModuleNotFoundError` raised by several functions in this
106+
module when queried for a distribution package which is not installed in the
107+
current Python environment.
108+
109+
103110
Functional API
104111
==============
105112

@@ -111,31 +118,53 @@ This package provides the following functionality via its public API.
111118
Entry points
112119
------------
113120

114-
The ``entry_points()`` function returns a collection of entry points.
115-
Entry points are represented by ``EntryPoint`` instances;
116-
each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
117-
a ``.load()`` method to resolve the value. There are also ``.module``,
118-
``.attr``, and ``.extras`` attributes for getting the components of the
119-
``.value`` attribute.
121+
.. function:: entry_points(**select_params)
122+
123+
Returns a :class:`EntryPoints` instance describing entry points for the
124+
current environment. Any given keyword parameters are passed to the
125+
:meth:`!~EntryPoints.select` method for comparison to the attributes of
126+
the individual entry point definitions.
127+
128+
Note: it is not currently possible to query for entry points based on
129+
their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution`
130+
instances do not currently compare equal, even if they have the same attributes)
131+
132+
.. class:: EntryPoints
133+
134+
Details of a collection of installed entry points.
135+
136+
Also provides a ``.groups`` attribute that reports all identifed entry
137+
point groups, and a ``.names`` attribute that reports all identified entry
138+
point names.
139+
140+
.. class:: EntryPoint
141+
142+
Details of an installed entry point.
143+
144+
Each :class:`!EntryPoint` instance has ``.name``, ``.group``, and ``.value``
145+
attributes and a ``.load()`` method to resolve the value. There are also
146+
``.module``, ``.attr``, and ``.extras`` attributes for getting the
147+
components of the ``.value`` attribute, and ``.dist`` for obtaining
148+
information regarding the distribution package that provides the entry point.
120149

121150
Query all entry points::
122151

123152
>>> eps = entry_points() # doctest: +SKIP
124153

125-
The ``entry_points()`` function returns an ``EntryPoints`` object,
126-
a collection of all ``EntryPoint`` objects with ``names`` and ``groups``
154+
The :func:`!entry_points` function returns a :class:`!EntryPoints` object,
155+
a collection of all :class:`!EntryPoint` objects with ``names`` and ``groups``
127156
attributes for convenience::
128157

129158
>>> sorted(eps.groups) # doctest: +SKIP
130159
['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
131160

132-
``EntryPoints`` has a ``select`` method to select entry points
161+
:class:`!EntryPoints` has a :meth:`!~EntryPoints.select` method to select entry points
133162
matching specific properties. Select entry points in the
134163
``console_scripts`` group::
135164

136165
>>> scripts = eps.select(group='console_scripts') # doctest: +SKIP
137166

138-
Equivalently, since ``entry_points`` passes keyword arguments
167+
Equivalently, since :func:`!entry_points` passes keyword arguments
139168
through to select::
140169

141170
>>> scripts = entry_points(group='console_scripts') # doctest: +SKIP
@@ -187,31 +216,41 @@ for compatibility options.
187216
Distribution metadata
188217
---------------------
189218

190-
Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ includes some metadata,
191-
which you can extract using the
192-
``metadata()`` function::
219+
.. function:: metadata(distribution_name)
220+
221+
Return the distribution metadata corresponding to the named
222+
distribution package as a :class:`PackageMetadata` instance.
223+
224+
Raises :exc:`PackageNotFoundError` if the named distribution
225+
package is not installed in the current Python environment.
226+
227+
.. class:: PackageMetadata
228+
229+
A concrete implementation of the
230+
`PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`_.
231+
232+
In addition to providing the defined protocol methods and attributes, subscripting
233+
the instance is equivalent to calling the :meth:`!~PackageMetadata.get` method.
234+
235+
Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
236+
includes some metadata, which you can extract using the :func:`!metadata` function::
193237

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

196-
The keys of the returned data structure, a ``PackageMetadata``,
197-
name the metadata keywords, and
240+
The keys of the returned data structure name the metadata keywords, and
198241
the values are returned unparsed from the distribution metadata::
199242

200243
>>> wheel_metadata['Requires-Python'] # doctest: +SKIP
201244
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
202245

203-
``PackageMetadata`` also presents a ``json`` attribute that returns
246+
:class:`PackageMetadata` also presents a :attr:`!~PackageMetadata.json` attribute that returns
204247
all the metadata in a JSON-compatible form per :PEP:`566`::
205248

206249
>>> wheel_metadata.json['requires_python']
207250
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
208251

209-
.. note::
210-
211-
The actual type of the object returned by ``metadata()`` is an
212-
implementation detail and should be accessed only through the interface
213-
described by the
214-
`PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`_.
252+
The full set of available metadata is not described here.
253+
See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
215254

216255
.. versionchanged:: 3.10
217256
The ``Description`` is now included in the metadata when presented
@@ -225,7 +264,15 @@ all the metadata in a JSON-compatible form per :PEP:`566`::
225264
Distribution versions
226265
---------------------
227266

228-
The ``version()`` function is the quickest way to get a
267+
.. function:: version(distribution_name)
268+
269+
Return the installed distribution package version for the named
270+
distribution package.
271+
272+
Raises :exc:`PackageNotFoundError` if the named distribution
273+
package is not installed in the current Python environment.
274+
275+
The :func:`!version` function is the quickest way to get a
229276
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_'s version
230277
number, as a string::
231278

@@ -238,12 +285,28 @@ number, as a string::
238285
Distribution files
239286
------------------
240287

241-
You can also get the full set of files contained within a distribution. The
242-
``files()`` function takes a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ name
243-
and returns all of the
244-
files installed by this distribution. Each file object returned is a
245-
``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``,
246-
``size``, and ``hash`` properties as indicated by the metadata. For example::
288+
.. function:: files(distribution_name)
289+
290+
Return the full set of files contained within the named
291+
distribution package.
292+
293+
Raises :exc:`PackageNotFoundError` if the named distribution
294+
package is not installed in the current Python environment.
295+
296+
Returns :const:`None` if the distribution is found but the installation
297+
database records reporting the files associated with the distribuion package
298+
are missing.
299+
300+
.. class:: PackagePath
301+
302+
A :class:`pathlib.PurePath` derived object with additional ``dist``,
303+
``size``, and ``hash`` properties corresponding to the distribution
304+
package's installation metadata for that file.
305+
306+
The :func:`!files` function takes a
307+
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
308+
name and returns all of the files installed by this distribution. Each file is reported
309+
as a :class:`PackagePath` instance. For example::
247310

248311
>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP
249312
>>> util # doctest: +SKIP
@@ -266,16 +329,16 @@ Once you have the file, you can also read its contents::
266329
return s.encode('utf-8')
267330
return s
268331

269-
You can also use the ``locate`` method to get a the absolute path to the
270-
file::
332+
You can also use the :meth:`!~PackagePath.locate` method to get the absolute
333+
path to the file::
271334

272335
>>> util.locate() # doctest: +SKIP
273336
PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
274337

275338
In the case where the metadata file listing files
276-
(RECORD or SOURCES.txt) is missing, ``files()`` will
277-
return ``None``. The caller may wish to wrap calls to
278-
``files()`` in `always_iterable
339+
(``RECORD`` or ``SOURCES.txt``) is missing, :func:`!files` will
340+
return :const:`None`. The caller may wish to wrap calls to
341+
:func:`!files` in `always_iterable
279342
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
280343
or otherwise guard against this condition if the target
281344
distribution is not known to have the metadata present.
@@ -285,8 +348,16 @@ distribution is not known to have the metadata present.
285348
Distribution requirements
286349
-------------------------
287350

351+
.. function:: requires(distribution_name)
352+
353+
Return the declared dependency specifiers for the named
354+
distribution package.
355+
356+
Raises :exc:`PackageNotFoundError` if the named distribution
357+
package is not installed in the current Python environment.
358+
288359
To get the full set of requirements for a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_,
289-
use the ``requires()``
360+
use the :func:`!requires`
290361
function::
291362

292363
>>> requires('wheel') # doctest: +SKIP
@@ -299,6 +370,16 @@ function::
299370
Mapping import to distribution packages
300371
---------------------------------------
301372

373+
.. function:: packages_distributions()
374+
375+
Return a mapping from the top level module and import package
376+
names found via :attr:`sys.meta_path` to the names of the distribution
377+
packages (if any) that provide the corresponding files.
378+
379+
To allow for namespace packages (which may have members provided by
380+
multiple distribution packages), each top level import name maps to a
381+
list of distribution names rather than mapping directly to a single name.
382+
302383
A convenience method to resolve the `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
303384
name (or names, in the case of a namespace package)
304385
that provide each importable top-level
@@ -318,31 +399,50 @@ function is not reliable with such installs.
318399
Distributions
319400
=============
320401

321-
While the above API is the most common and convenient usage, you can get all
322-
of that information from the ``Distribution`` class. A ``Distribution`` is an
323-
abstract object that represents the metadata for
324-
a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_. You can
325-
get the ``Distribution`` instance::
402+
.. function:: distribution(distribution_name)
403+
404+
Return a :class:`Distribution` instance describing the named
405+
distribution package.
406+
407+
Raises :exc:`PackageNotFoundError` if the named distribution
408+
package is not installed in the current Python environment.
409+
410+
.. class:: Distribution
411+
412+
Details of an installed distribution package.
413+
414+
Note: different :class:`!Distribution` instances do not currently compare
415+
equal, even if they relate to the same installed distribution and
416+
accordingly have the same attributes.
417+
418+
While the module level API described above is the most common and convenient usage,
419+
you can get all of that information from the :class:`!Distribution` class.
420+
:class:`!Distribution` is an abstract object that represents the metadata for
421+
a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_.
422+
You can get the concreate :class:`!Distribution` subclass instance for an installed
423+
distribution package by calling the :func:`distribution` function::
326424

327425
>>> from importlib.metadata import distribution # doctest: +SKIP
328426
>>> dist = distribution('wheel') # doctest: +SKIP
427+
>>> type(dist) # doctest: +SKIP
428+
<class 'importlib.metadata.PathDistribution'>
329429

330430
Thus, an alternative way to get the version number is through the
331-
``Distribution`` instance::
431+
:class:`!Distribution` instance::
332432

333433
>>> dist.version # doctest: +SKIP
334434
'0.32.3'
335435

336-
There are all kinds of additional metadata available on the ``Distribution``
337-
instance::
436+
There are all kinds of additional metadata available on :class:`!Distribution`
437+
instances::
338438

339439
>>> dist.metadata['Requires-Python'] # doctest: +SKIP
340440
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
341441
>>> dist.metadata['License'] # doctest: +SKIP
342442
'MIT'
343443

344444
The full set of available metadata is not described here.
345-
See the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
445+
See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
346446

347447

348448
Distribution Discovery
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The :mod:`importlib.metadata` documentation now includes semantic
2+
cross-reference targets for the significant documented APIs. This means
3+
intersphinx references like :func:`importlib.metadata.version` will
4+
now work as expected.

0 commit comments

Comments
 (0)