[3.13] gh-125018: Add importlib.metadata semantic link targets (GH-125027) (#125047)

gh-125018: Add importlib.metadata semantic link targets (#125027)

This allows direct intersphinx references to APIs via references
like `` :func:`importlib.metadata.version` ``.

(cherry picked from commit cda3b5a576412a8671bbe4c68bb792ec14f1a4b1)

---------

Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst
index 9c0879f..5aa9559 100644
--- a/Doc/library/importlib.metadata.rst
+++ b/Doc/library/importlib.metadata.rst
@@ -100,6 +100,13 @@
 :ref:`requirements`.
 
 
+.. exception:: PackageNotFoundError
+
+   Subclass of :class:`ModuleNotFoundError` raised by several functions in this
+   module when queried for a distribution package which is not installed in the
+   current Python environment.
+
+
 Functional API
 ==============
 
@@ -111,31 +118,53 @@
 Entry points
 ------------
 
-The ``entry_points()`` function returns a collection of entry points.
-Entry points are represented by ``EntryPoint`` instances;
-each ``EntryPoint`` has a ``.name``, ``.group``, and ``.value`` attributes and
-a ``.load()`` method to resolve the value.  There are also ``.module``,
-``.attr``, and ``.extras`` attributes for getting the components of the
-``.value`` attribute.
+.. function:: entry_points(**select_params)
+
+   Returns a :class:`EntryPoints` instance describing entry points for the
+   current environment. Any given keyword parameters are passed to the
+   :meth:`!~EntryPoints.select` method for comparison to the attributes of
+   the individual entry point definitions.
+
+   Note: it is not currently possible to query for entry points based on
+   their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution`
+   instances do not currently compare equal, even if they have the same attributes)
+
+.. class:: EntryPoints
+
+   Details of a collection of installed entry points.
+
+   Also provides a ``.groups`` attribute that reports all identifed entry
+   point groups, and a ``.names`` attribute that reports all identified entry
+   point names.
+
+.. class:: EntryPoint
+
+   Details of an installed entry point.
+
+   Each :class:`!EntryPoint` instance has ``.name``, ``.group``, and ``.value``
+   attributes and a ``.load()`` method to resolve the value. There are also
+   ``.module``, ``.attr``, and ``.extras`` attributes for getting the
+   components of the ``.value`` attribute, and ``.dist`` for obtaining
+   information regarding the distribution package that provides the entry point.
 
 Query all entry points::
 
     >>> eps = entry_points()  # doctest: +SKIP
 
-The ``entry_points()`` function returns an ``EntryPoints`` object,
-a collection of all ``EntryPoint`` objects with ``names`` and ``groups``
+The :func:`!entry_points` function returns a :class:`!EntryPoints` object,
+a collection of all :class:`!EntryPoint` objects with ``names`` and ``groups``
 attributes for convenience::
 
     >>> sorted(eps.groups)  # doctest: +SKIP
     ['console_scripts', 'distutils.commands', 'distutils.setup_keywords', 'egg_info.writers', 'setuptools.installation']
 
-``EntryPoints`` has a ``select`` method to select entry points
+:class:`!EntryPoints` has a :meth:`!~EntryPoints.select` method to select entry points
 matching specific properties. Select entry points in the
 ``console_scripts`` group::
 
     >>> scripts = eps.select(group='console_scripts')  # doctest: +SKIP
 
-Equivalently, since ``entry_points`` passes keyword arguments
+Equivalently, since :func:`!entry_points` passes keyword arguments
 through to select::
 
     >>> scripts = entry_points(group='console_scripts')  # doctest: +SKIP
@@ -189,31 +218,41 @@
 Distribution metadata
 ---------------------
 
-Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ includes some metadata,
-which you can extract using the
-``metadata()`` function::
+.. function:: metadata(distribution_name)
+
+   Return the distribution metadata corresponding to the named
+   distribution package as a :class:`PackageMetadata` instance.
+
+   Raises :exc:`PackageNotFoundError` if the named distribution
+   package is not installed in the current Python environment.
+
+.. class:: PackageMetadata
+
+   A concrete implementation of the
+    `PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`_.
+
+    In addition to providing the defined protocol methods and attributes, subscripting
+    the instance is equivalent to calling the :meth:`!~PackageMetadata.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::
 
     >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP
 
-The keys of the returned data structure, a ``PackageMetadata``,
-name the metadata keywords, and
+The keys of the returned data structure name the metadata keywords, and
 the values are returned unparsed from the distribution metadata::
 
     >>> wheel_metadata['Requires-Python']  # doctest: +SKIP
     '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
 
-``PackageMetadata`` also presents a ``json`` attribute that returns
+:class:`PackageMetadata` also presents a :attr:`!~PackageMetadata.json` attribute that returns
 all the metadata in a JSON-compatible form per :PEP:`566`::
 
     >>> wheel_metadata.json['requires_python']
     '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
 
-.. note::
-
-    The actual type of the object returned by ``metadata()`` is an
-    implementation detail and should be accessed only through the interface
-    described by the
-    `PackageMetadata protocol <https://importlib-metadata.readthedocs.io/en/latest/api.html#importlib_metadata.PackageMetadata>`_.
+The full set of available metadata is not described here.
+See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
 
 .. versionchanged:: 3.10
    The ``Description`` is now included in the metadata when presented
@@ -227,7 +266,15 @@
 Distribution versions
 ---------------------
 
-The ``version()`` function is the quickest way to get a
+.. function:: version(distribution_name)
+
+   Return the installed distribution package version for the named
+   distribution package.
+
+   Raises :exc:`PackageNotFoundError` if the named distribution
+   package is not installed in the current Python environment.
+
+The :func:`!version` function is the quickest way to get a
 `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_'s version
 number, as a string::
 
@@ -240,12 +287,28 @@
 Distribution files
 ------------------
 
-You can also get the full set of files contained within a distribution.  The
-``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.  Each file object returned is a
-``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``,
-``size``, and ``hash`` properties as indicated by the metadata.  For example::
+.. function:: files(distribution_name)
+
+   Return the full set of files contained within the named
+   distribution package.
+
+   Raises :exc:`PackageNotFoundError` if the named distribution
+   package is not installed in the current Python environment.
+
+   Returns :const:`None` if the distribution is found but the installation
+   database records reporting the files associated with the distribuion package
+   are missing.
+
+.. class:: PackagePath
+
+    A :class:`pathlib.PurePath` derived object with additional ``dist``,
+    ``size``, and ``hash`` properties corresponding to the distribution
+    package's installation metadata for that file.
+
+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. Each file is reported
+as a :class:`PackagePath` instance. For example::
 
     >>> util = [p for p in files('wheel') if 'util.py' in str(p)][0]  # doctest: +SKIP
     >>> util  # doctest: +SKIP
@@ -268,16 +331,16 @@
             return s.encode('utf-8')
         return s
 
-You can also use the ``locate`` method to get a the absolute path to the
-file::
+You can also use the :meth:`!~PackagePath.locate` method to get the absolute
+path to the file::
 
     >>> util.locate()  # doctest: +SKIP
     PosixPath('/home/gustav/example/lib/site-packages/wheel/util.py')
 
 In the case where the metadata file listing files
-(RECORD or SOURCES.txt) is missing, ``files()`` will
-return ``None``. The caller may wish to wrap calls to
-``files()`` in `always_iterable
+(``RECORD`` or ``SOURCES.txt``) is missing, :func:`!files` will
+return :const:`None`. The caller may wish to wrap calls to
+:func:`!files` in `always_iterable
 <https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.always_iterable>`_
 or otherwise guard against this condition if the target
 distribution is not known to have the metadata present.
@@ -287,8 +350,16 @@
 Distribution requirements
 -------------------------
 
+.. function:: requires(distribution_name)
+
+   Return the declared dependency specifiers for the named
+   distribution package.
+
+   Raises :exc:`PackageNotFoundError` if the named distribution
+   package is not installed in the current Python environment.
+
 To get the full set of requirements for a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_,
-use the ``requires()``
+use the :func:`!requires`
 function::
 
     >>> requires('wheel')  # doctest: +SKIP
@@ -301,6 +372,16 @@
 Mapping import to distribution packages
 ---------------------------------------
 
+.. function:: packages_distributions()
+
+   Return a mapping from the top level module and import package
+   names found via :attr:`sys.meta_path` to the names of the distribution
+   packages (if any) that provide the corresponding files.
+
+   To allow for namespace packages (which may have members provided by
+   multiple distribution packages), each top level import name maps to a
+   list of distribution names rather than mapping directly to a single name.
+
 A convenience method to resolve the `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_
 name (or names, in the case of a namespace package)
 that provide each importable top-level
@@ -320,23 +401,42 @@
 Distributions
 =============
 
-While the above API is the most common and convenient usage, you can get all
-of that information from the ``Distribution`` class.  A ``Distribution`` is an
-abstract object that represents the metadata for
-a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_.  You can
-get the ``Distribution`` instance::
+.. function:: distribution(distribution_name)
+
+   Return a :class:`Distribution` instance describing the named
+   distribution package.
+
+   Raises :exc:`PackageNotFoundError` if the named distribution
+   package is not installed in the current Python environment.
+
+.. class:: Distribution
+
+   Details of an installed distribution package.
+
+   Note: different :class:`!Distribution` instances do not currently compare
+   equal, even if they relate to the same installed distribution and
+   accordingly have the same attributes.
+
+While the module level API described above is the most common and convenient usage,
+you can get all of that information from the :class:`!Distribution` class.
+:class:`!Distribution` is an abstract object that represents the metadata for
+a Python `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_.
+You can get the concreate :class:`!Distribution` subclass instance for an installed
+distribution package by calling the :func:`distribution` function::
 
     >>> from importlib.metadata import distribution  # doctest: +SKIP
     >>> dist = distribution('wheel')  # doctest: +SKIP
+    >>> type(dist)  # doctest: +SKIP
+    <class 'importlib.metadata.PathDistribution'>
 
 Thus, an alternative way to get the version number is through the
-``Distribution`` instance::
+:class:`!Distribution` instance::
 
     >>> dist.version  # doctest: +SKIP
     '0.32.3'
 
-There are all kinds of additional metadata available on the ``Distribution``
-instance::
+There are all kinds of additional metadata available on :class:`!Distribution`
+instances::
 
     >>> dist.metadata['Requires-Python']  # doctest: +SKIP
     '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
@@ -350,7 +450,7 @@
     'file:///path/to/wheel-0.32.3.editable-py3-none-any.whl'
 
 The full set of available metadata is not described here.
-See the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
+See the PyPA `Core metadata specification <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details.
 
 .. versionadded:: 3.13
    The ``.origin`` property was added.
diff --git a/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst
new file mode 100644
index 0000000..e910da5
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst
@@ -0,0 +1,4 @@
+The :mod:`importlib.metadata` documentation now includes semantic
+cross-reference targets for the significant documented APIs. This means
+intersphinx references like :func:`importlib.metadata.version` will
+now work as expected.