From 754c51027aaad9ea64ca31ab3c0dbef2aac52b21 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 00:22:18 +1000 Subject: [PATCH 1/6] gh-125018: Add importlib.metadata semantic link targets This allows direct intersphinx references to APIs via references like `` :func:`importlib.metadata.version` ``. --- Doc/library/importlib.metadata.rst | 98 +++++++++++++++++-- ...-10-07-00-31-17.gh-issue-125018.yKnymn.rst | 4 + 2 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 66ba621084c898..47c32dddf072a7 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -100,6 +100,13 @@ You can also get a :ref:`distribution's version number `, list its :ref:`requirements`. +.. class:: 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,12 +118,31 @@ This package provides the following functionality via its public API. Entry points ------------ -The ``entry_points()`` function returns a collection of entry points. +.. class:: EntryPoint + + Details of an installed entry point (as described below). + +.. class:: EntryPoints + + Details of a collection of installed entry points (as described below). + +.. function:: entry_points(**select_params) + + Returns a :class:`EntryPoints` instance describing entry points for the + current environment. Any given keyword parameters are passed to + ``EntryPoints.select`` for comparison to the attributes of the + individual entry point definitions (as described below). + + Note: it is not currently possible to query for entry points based on + their ``dist`` attribute (as different ``Distribution`` instances do + not compare equal, even if they have the same attributes) + 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. +``.value`` attribute, and ``.dist`` for obtaining information regarding +the distribution package that provides the entry point. Query all entry points:: @@ -189,9 +215,16 @@ for more information on entry points, their definition, and usage. Distribution metadata --------------------- -Every `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. + + Raises :class:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + +Every `Distribution Package `_ +includes some metadata, which you can extract using the ``metadata()`` function:: >>> wheel_metadata = metadata('wheel') # doctest: +SKIP @@ -227,6 +260,14 @@ all the metadata in a JSON-compatible form per :PEP:`566`:: Distribution versions --------------------- +.. function:: version(distribution_name) + + Return the installed distribution package version for the named + distribution package. + + Raises :class:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + The ``version()`` function is the quickest way to get a `Distribution Package `_'s version number, as a string:: @@ -240,9 +281,17 @@ number, as a string:: Distribution files ------------------ -You can also get the full set of files contained within a distribution. The -``files()`` function takes a `Distribution Package `_ name -and returns all of the +.. function:: files(distribution_name) + + Return the full set of files contained within the named + distribution package. + + Raises :class:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + +The ``files()`` function takes a +`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:: @@ -287,6 +336,14 @@ distribution is not known to have the metadata present. Distribution requirements ------------------------- +.. function:: requires(distribution_name) + + Return the declared dependency specifiers for the named + distribution package. + + Raises :class:`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 `_, use the ``requires()`` function:: @@ -301,6 +358,15 @@ function:: Mapping import to distribution packages --------------------------------------- +.. function:: packages_distributions() + + Return a mapping from importable top level module and import package + names 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 module + name maps to a list of distribution names rather than mapping directly + to a single name. + A convenience method to resolve the `Distribution Package `_ name (or names, in the case of a namespace package) that provide each importable top-level @@ -320,6 +386,22 @@ function is not reliable with such installs. Distributions ============= +.. class:: Distribution + + Details of an installed distribution package (as described below). + + Note: different ``Distribution`` instances do not currently compare + equal, even if they relate to the same installed distribution (and + hence have the same attributes) + +.. function:: distribution(distribution_name) + + Return a :class:`Distribution` instance describing the named + distribution package. + + Raises :class:`PackageNotFoundError` if the named distribution + package is not installed in the current Python environment. + 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 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 00000000000000..8f24d6ed5aca03 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst @@ -0,0 +1,4 @@ +The ``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. From 8934921fbcf6ed3825b6a494406c779c1ccd813a Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 00:57:17 +1000 Subject: [PATCH 2/6] NEWS markup fix --- .../2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8f24d6ed5aca03..0004193ef60335 100644 --- 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 @@ -1,4 +1,4 @@ The ``importlib.metadata`` documentation now includes semantic cross-reference targets for the significant documented APIs. This means -intersphinx references like ``:func:`importlib.metadata.version`\ `` will +intersphinx references like :func:`importlib.metadata.version` will now work as expected. From 213179bc738276ecc2ab69b7cdd0fdc99bf1f32a Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 16:35:36 +1000 Subject: [PATCH 3/6] Use module docs link in NEWS entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- .../2024-10-07-00-31-17.gh-issue-125018.yKnymn.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0004193ef60335..e910da5b879ba5 100644 --- 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 @@ -1,4 +1,4 @@ -The ``importlib.metadata`` documentation now includes semantic +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. From 89311805bb43618d2a7efded9eda824d904a0848 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 16:36:10 +1000 Subject: [PATCH 4/6] Mark PackageNotFoundError as an exception Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Doc/library/importlib.metadata.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 47c32dddf072a7..757de56a32ffb5 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -100,7 +100,7 @@ You can also get a :ref:`distribution's version number `, list its :ref:`requirements`. -.. class:: PackageNotFoundError +.. 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 From cad0e15ca9901db50c7369c93baf163b982d38f5 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 17:44:18 +1000 Subject: [PATCH 5/6] Address misc review comments --- Doc/library/importlib.metadata.rst | 162 ++++++++++++++++------------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 757de56a32ffb5..82b9e6150033d0 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -118,50 +118,53 @@ This package provides the following functionality via its public API. Entry points ------------ -.. class:: EntryPoint +.. 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. - Details of an installed entry point (as described below). + 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 (as described below). + Details of a collection of installed entry points. -.. function:: entry_points(**select_params) + Also provides a ``.groups`` attribute that reports all identifed entry + point groups, and a ``.names`` attribute that reports all identified entry + point names. - Returns a :class:`EntryPoints` instance describing entry points for the - current environment. Any given keyword parameters are passed to - ``EntryPoints.select`` for comparison to the attributes of the - individual entry point definitions (as described below). +.. class:: EntryPoint - Note: it is not currently possible to query for entry points based on - their ``dist`` attribute (as different ``Distribution`` instances do - not compare equal, even if they have the same attributes) + Details of an installed entry point. -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, and ``.dist`` for obtaining information regarding -the distribution package that provides the 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 @@ -218,35 +221,38 @@ Distribution metadata .. function:: metadata(distribution_name) Return the distribution metadata corresponding to the named - distribution package. + distribution package as a :class:`PackageMetadata` instance. - Raises :class:`PackageNotFoundError` if the named distribution + 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 `_. + + 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 `_ -includes some metadata, which you can extract using the ``metadata()`` function:: +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 `_. +The full set of available metadata is not described here. +See the PyPA `Core metadata specification `_ for additional details. .. versionchanged:: 3.10 The ``Description`` is now included in the metadata when presented @@ -265,10 +271,10 @@ Distribution versions Return the installed distribution package version for the named distribution package. - Raises :class:`PackageNotFoundError` if the named distribution + Raises :exc:`PackageNotFoundError` if the named distribution package is not installed in the current Python environment. -The ``version()`` function is the quickest way to get a +The :func:`!version` function is the quickest way to get a `Distribution Package `_'s version number, as a string:: @@ -286,15 +292,23 @@ Distribution files Return the full set of files contained within the named distribution package. - Raises :class:`PackageNotFoundError` if the named distribution + Raises :exc:`PackageNotFoundError` if the named distribution package is not installed in the current Python environment. -The ``files()`` function takes a + 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 `_ -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:: +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 @@ -317,16 +331,16 @@ Once you have the file, you can also read its contents:: 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 `_ or otherwise guard against this condition if the target distribution is not known to have the metadata present. @@ -341,11 +355,11 @@ Distribution requirements Return the declared dependency specifiers for the named distribution package. - Raises :class:`PackageNotFoundError` if the named distribution + 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 `_, -use the ``requires()`` +use the :func:`!requires` function:: >>> requires('wheel') # doctest: +SKIP @@ -360,12 +374,13 @@ Mapping import to distribution packages .. function:: packages_distributions() - Return a mapping from importable top level module and import package - names 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 module - name maps to a list of distribution names rather than mapping directly - to a single name. + Return a mapping from the top level module and import package + names found via :attr:`sys.metapath` 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 `_ name (or names, in the case of a namespace package) @@ -386,39 +401,42 @@ function is not reliable with such installs. Distributions ============= -.. class:: Distribution - - Details of an installed distribution package (as described below). - - Note: different ``Distribution`` instances do not currently compare - equal, even if they relate to the same installed distribution (and - hence have the same attributes) - .. function:: distribution(distribution_name) Return a :class:`Distribution` instance describing the named distribution package. - Raises :class:`PackageNotFoundError` if the named distribution + Raises :exc:`PackageNotFoundError` if the named distribution package is not installed in the current Python environment. -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 `_. You can -get the ``Distribution`` instance:: +.. 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 `_. +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 + 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.*' @@ -432,7 +450,7 @@ metadata:: '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 `_ for additional details. +See the PyPA `Core metadata specification `_ for additional details. .. versionadded:: 3.13 The ``.origin`` property was added. From 87ccae20cbf4a63df2c6e20e15066755de7b5a09 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Mon, 7 Oct 2024 22:58:38 +1000 Subject: [PATCH 6/6] Fix Sphinx issues --- Doc/library/importlib.metadata.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index 82b9e6150033d0..ddb72784694fd9 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -126,7 +126,7 @@ Entry points 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` + their :attr:`!EntryPoint.dist` attribute (as different :class:`!Distribution` instances do not currently compare equal, even if they have the same attributes) .. class:: EntryPoints @@ -305,7 +305,7 @@ Distribution files ``size``, and ``hash`` properties corresponding to the distribution package's installation metadata for that file. -The :func:`!files()` function takes a +The :func:`!files` function takes a `Distribution Package `_ name and returns all of the files installed by this distribution. Each file is reported as a :class:`PackagePath` instance. For example:: @@ -338,9 +338,9 @@ path to the file:: 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, :func:`!files()` will +(``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 +:func:`!files` in `always_iterable `_ or otherwise guard against this condition if the target distribution is not known to have the metadata present. @@ -375,7 +375,7 @@ Mapping import to distribution packages .. function:: packages_distributions() Return a mapping from the top level module and import package - names found via :attr:`sys.metapath` to the names of the distribution + 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