Skip to content

Commit ae7fc4a

Browse files
authored
gh-110937: Document full public importlib.metadata.Distribution API (#143480)
1 parent 0e4b1ba commit ae7fc4a

File tree

3 files changed

+104
-40
lines changed

3 files changed

+104
-40
lines changed

Doc/library/importlib.metadata.rst

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ Entry points
125125
:meth:`!select` method for comparison to the attributes of
126126
the individual entry point definitions.
127127

128-
Note: it is not currently possible to query for entry points based on
129-
their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution`
128+
Note: to query for entry points based on :attr:`!EntryPoint.dist` attribute,
129+
use :meth:`Distribution.entry_points` instead (as different :class:`Distribution`
130130
instances do not currently compare equal, even if they have the same attributes)
131131

132132
.. class:: EntryPoints
@@ -291,7 +291,7 @@ Distribution files
291291
.. function:: files(distribution_name)
292292

293293
Return the full set of files contained within the named
294-
distribution package.
294+
distribution package as :class:`PackagePath` instances.
295295

296296
Raises :exc:`PackageNotFoundError` if the named distribution
297297
package is not installed in the current Python environment.
@@ -304,12 +304,22 @@ Distribution files
304304

305305
A :class:`pathlib.PurePath` derived object with additional ``dist``,
306306
``size``, and ``hash`` properties corresponding to the distribution
307-
package's installation metadata for that file.
307+
package's installation metadata for that file, also:
308+
309+
.. method:: locate()
310+
311+
If possible, return the concrete :class:`SimplePath` allowing to access data,
312+
or raise a :exc:`NotImplementedError` otherwise.
313+
314+
.. class:: SimplePath
315+
316+
A protocol representing a minimal subset of :class:`pathlib.Path` that allows to
317+
check if it ``exists()``, to traverse using ``joinpath()`` and ``parent``,
318+
and to retrieve data using ``read_text()`` and ``read_bytes()``.
308319

309320
The :func:`!files` function takes a
310321
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
311-
name and returns all of the files installed by this distribution. Each file is reported
312-
as a :class:`PackagePath` instance. For example::
322+
name and returns all of the files installed by this distribution. For example::
313323

314324
>>> util = [p for p in files('wheel') if 'util.py' in str(p)][0] # doctest: +SKIP
315325
>>> util # doctest: +SKIP
@@ -402,6 +412,18 @@ function is not reliable with such installs.
402412
Distributions
403413
=============
404414

415+
While the module level API described above is the most common and convenient usage,
416+
all that information is accessible from the :class:`Distribution` class.
417+
:class:`!Distribution` is an abstract object that represents the metadata for
418+
a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_.
419+
Get the concrete :class:`!Distribution` subclass instance for an installed
420+
distribution package by calling the :func:`distribution` function::
421+
422+
>>> from importlib.metadata import distribution # doctest: +SKIP
423+
>>> dist = distribution('wheel') # doctest: +SKIP
424+
>>> type(dist) # doctest: +SKIP
425+
<class 'importlib.metadata.PathDistribution'>
426+
405427
.. function:: distribution(distribution_name)
406428

407429
Return a :class:`Distribution` instance describing the named
@@ -410,6 +432,14 @@ Distributions
410432
Raises :exc:`PackageNotFoundError` if the named distribution
411433
package is not installed in the current Python environment.
412434

435+
Thus, an alternative way to get e.g. the version number is through the
436+
:attr:`Distribution.version` attribute::
437+
438+
>>> dist.version # doctest: +SKIP
439+
'0.32.3'
440+
441+
The same applies for :func:`entry_points` and :func:`files`.
442+
413443
.. class:: Distribution
414444

415445
Details of an installed distribution package.
@@ -418,53 +448,85 @@ Distributions
418448
equal, even if they relate to the same installed distribution and
419449
accordingly have the same attributes.
420450

421-
.. method:: discover(cls, *, context=None, **kwargs)
451+
.. staticmethod:: at(path)
452+
.. classmethod:: from_name(name)
453+
454+
Return a :class:`!Distribution` instance at the given path or
455+
with the given name.
422456

423-
Returns an iterable of :class:`Distribution` instances for all packages.
457+
.. classmethod:: discover(*, context=None, **kwargs)
458+
459+
Returns an iterable of :class:`!Distribution` instances for all packages
460+
(see distribution-discovery_).
424461

425462
The optional argument *context* is a :class:`DistributionFinder.Context`
426463
instance, used to modify the search for distributions. Alternatively,
427464
*kwargs* may contain keyword arguments for constructing a new
428465
:class:`!DistributionFinder.Context`.
429466

467+
.. attribute:: metadata
468+
:type: PackageMetadata
430469

431-
While the module level API described above is the most common and convenient usage,
432-
you can get all of that information from the :class:`!Distribution` class.
433-
:class:`!Distribution` is an abstract object that represents the metadata for
434-
a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_.
435-
You can get the concrete :class:`!Distribution` subclass instance for an installed
436-
distribution package by calling the :func:`distribution` function::
470+
There are all kinds of additional metadata available on :class:`!Distribution`
471+
instances as a :class:`PackageMetadata` instance::
437472

438-
>>> from importlib.metadata import distribution # doctest: +SKIP
439-
>>> dist = distribution('wheel') # doctest: +SKIP
440-
>>> type(dist) # doctest: +SKIP
441-
<class 'importlib.metadata.PathDistribution'>
473+
>>> dist.metadata['Requires-Python'] # doctest: +SKIP
474+
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
475+
>>> dist.metadata['License'] # doctest: +SKIP
476+
'MIT'
442477

443-
Thus, an alternative way to get the version number is through the
444-
:class:`!Distribution` instance::
478+
The full set of available metadata is not described here.
479+
See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
445480

446-
>>> dist.version # doctest: +SKIP
447-
'0.32.3'
481+
.. attribute:: name
482+
:type: str
483+
.. attribute:: requires
484+
:type: list[str]
485+
.. attribute:: version
486+
:type: str
448487

449-
There are all kinds of additional metadata available on :class:`!Distribution`
450-
instances::
488+
A few metadata fields are also available as shortcut properties.
451489

452-
>>> dist.metadata['Requires-Python'] # doctest: +SKIP
453-
'>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
454-
>>> dist.metadata['License'] # doctest: +SKIP
455-
'MIT'
490+
.. versionadded:: 3.10
456491

457-
For editable packages, an ``origin`` property may present :pep:`610`
458-
metadata::
492+
The ``name`` shortcut was added.
459493

460-
>>> dist.origin.url
461-
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
494+
.. attribute:: origin
462495

463-
The full set of available metadata is not described here.
464-
See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
496+
For editable packages, an ``origin`` property may present :pep:`610`
497+
metadata (for non-editable packages, ``origin`` is :const:`None`)::
498+
499+
>>> dist.origin.url
500+
'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
501+
502+
The ``origin`` object follows the `Direct URL Data Structure
503+
<https://packaging.python.org/en/latest/specifications/direct-url-data-structure/>`_.
504+
505+
.. versionadded:: 3.13
506+
507+
.. attribute:: entry_points
508+
:type: EntryPoints
509+
510+
The entry points provided by this distribution package.
511+
512+
.. attribute:: files
513+
:type: list[PackagePath] | None
514+
515+
All files contained in this distribution package.
516+
Like :func:`files`, this returns :const:`None` if there are no records.
517+
518+
The following two abstract methods need to be implemented when implementing-custom-providers_:
519+
520+
.. method:: locate_file(path)
521+
522+
Like :meth:`!PackagePath.locate`, return a :class:`SimplePath` for the given path.
523+
Takes a :class:`os.PathLike` or a :class:`str`.
524+
525+
.. method:: read_text(filename)
526+
527+
A shortcut for ``distribution.locate_file(filename).read_text()``.
465528

466-
.. versionadded:: 3.13
467-
The ``.origin`` property was added.
529+
.. _distribution-discovery:
468530

469531
Distribution Discovery
470532
======================
@@ -575,8 +637,8 @@ consumer.
575637

576638
In practice, to support finding distribution package
577639
metadata in locations other than the file system, subclass
578-
``Distribution`` and implement the abstract methods. Then from
579-
a custom finder, return instances of this derived ``Distribution`` in the
640+
:class:`!Distribution` and implement the abstract methods. Then from
641+
a custom finder, return instances of this derived :class:`!Distribution` in the
580642
``find_distributions()`` method.
581643

582644
Example
@@ -653,8 +715,8 @@ packages served by the ``DatabaseImporter``, assuming that the
653715
``.entry_points`` attributes.
654716

655717
The ``DatabaseDistribution`` may also provide other metadata files, like
656-
``RECORD`` (required for ``Distribution.files``) or override the
657-
implementation of ``Distribution.files``. See the source for more inspiration.
718+
``RECORD`` (required for :attr:`!Distribution.files`) or override the
719+
implementation of :attr:`!Distribution.files`. See the source for more inspiration.
658720

659721

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

Lib/importlib/metadata/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
'DistributionFinder',
4242
'PackageMetadata',
4343
'PackageNotFoundError',
44+
'PackagePath',
4445
'SimplePath',
4546
'distribution',
4647
'distributions',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document rest of full public :class:`importlib.metadata.Distribution` API. Also add the (already documented) :class:`~importlib.metadata.PackagePath` to ``__all__``.

0 commit comments

Comments
 (0)