Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 80 additions & 4 deletions Doc/library/importlib.metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ Distribution metadata

.. class:: PackageMetadata

A concrete implementation of the
`PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`_.
A protocol describing the interface distribution metadata presents. The
:pypi:`importlib_metadata` backport presents the same protocol; see
:ref:`metadata-backport-caveats`.

In addition to providing the defined protocol methods and attributes, subscripting
the instance is equivalent to calling the :meth:`!get` method.
In addition to the protocol methods and attributes, subscripting an
instance is equivalent to calling the :meth:`!get` method.

Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
includes some metadata, which you can extract using the :func:`!metadata` function::
Expand Down Expand Up @@ -332,6 +333,13 @@ Distribution files
check if it ``exists()``, to traverse using ``joinpath()`` and ``parent``,
and to retrieve data using ``read_text()`` and ``read_bytes()``.

.. class:: IPackagePath

A protocol describing the interface a :class:`PackagePath` presents. See
:ref:`metadata-backport-caveats`.

.. versionadded:: 3.16

The :func:`!files` function takes a
`Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
name and returns all of the files installed by this distribution. For example::
Expand Down Expand Up @@ -544,6 +552,17 @@ The same applies for :func:`entry_points` and :func:`files`.

A shortcut for ``distribution.locate_file(filename).read_text()``.

.. class:: IDistribution

A protocol describing the interface a :class:`Distribution` presents.

A consumer that targets this protocol, rather than a concrete class, works
with distributions from either this module or the
:pypi:`importlib_metadata` backport, however they were supplied. See
:ref:`metadata-backport-caveats`.

.. versionadded:: 3.16

.. _distribution-discovery:

Distribution Discovery
Expand Down Expand Up @@ -735,3 +754,60 @@ packages served by the ``DatabaseImporter``, assuming that the
The ``DatabaseDistribution`` may also provide other metadata files, like
``RECORD`` (required for :attr:`!Distribution.files`) or override the
implementation of :attr:`!Distribution.files`. See the source for more inspiration.

``DatabaseDistribution`` derives from :class:`Distribution` as defined here,
which has consequences for consumers that use the :pypi:`importlib_metadata`
backport instead of this module. See :ref:`metadata-backport-providers`.


.. _metadata-backport-caveats:

Caveats for backports
=====================

The :pypi:`importlib_metadata` package backports this module and supplies its
own :class:`!Distribution`, :class:`!PackagePath`, and metadata classes. Those
classes are separate from the ones here; neither set derives from the other.
Both libraries discover distributions by querying the finders on
:data:`sys.meta_path`, so one custom provider serves consumers of both and has
no say in which of the two a given consumer uses.

Both libraries present the same protocols, :class:`IDistribution`,
:class:`IPackagePath`, and :class:`PackageMetadata`, and the classes of both
satisfy them. Those protocols, rather than any concrete class, are the
interface to write against.

For consumers
-------------

A consumer calling ``importlib_metadata.distributions()`` expects that
package's objects, but a custom provider may supply objects from the hierarchy
documented here. The backport settles most of this on the consumer's behalf,
substituting its own class for each object it recognizes, such as a
:class:`!PathDistribution`, a :class:`PackagePath`, or an
:class:`email.message.Message`. It has no way to reconstruct an arbitrary
:class:`Distribution` subclass from a custom provider, so it passes such an
object through unchanged.

Annotate against the protocols, then, and do not test an object with
:func:`isinstance` against a concrete class. Code that needs the protocols on
earlier Python versions can import them from the backport, which presents them
there too.

.. _metadata-backport-providers:

For custom providers
--------------------

A provider's objects reach consumers of both libraries, so supply objects that
satisfy :class:`IDistribution` whole, not in part. Deriving from
:class:`Distribution`, as the example above does, achieves this: the base
class builds the rest of the interface on :meth:`~Distribution.read_text` and
:meth:`~Distribution.locate_file`. Where a class omits a member the protocol
requires, the backport emits a :exc:`UserWarning` naming what is missing; a
provider satisfying the protocol whole draws no warning.

Deriving from whichever of the two libraries happens to be importable is not a
general answer. A provider cannot know whether a consumer will import the
backport, and importing it to find out activates it in environments that would
otherwise never have used it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Document the ``IDistribution``, ``IPackagePath``, and ``PackageMetadata``
protocols shared with the ``importlib_metadata`` backport, and how custom
providers and consumers of ``importlib.metadata`` should use them.
Loading