diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 1378a131f0ce65..3ab8505e1e9ebc 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -237,11 +237,12 @@ Distribution metadata .. class:: PackageMetadata - A concrete implementation of the - `PackageMetadata protocol `_. + 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 `_ includes some metadata, which you can extract using the :func:`!metadata` function:: @@ -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 `_ name and returns all of the files installed by this distribution. For example:: @@ -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 @@ -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. diff --git a/Misc/NEWS.d/next/Documentation/2026-10-22-18-51-05.gh-issue-123976.GKLjFT.rst b/Misc/NEWS.d/next/Documentation/2026-10-22-18-51-05.gh-issue-123976.GKLjFT.rst new file mode 100644 index 00000000000000..614d2c6595c324 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-10-22-18-51-05.gh-issue-123976.GKLjFT.rst @@ -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.