From 35d5068928ab5485e5f28b60b1e33062bc2c46cc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 14 Mar 2021 22:20:49 -0400 Subject: bpo-43428: Improve documentation for importlib.metadata changes. (GH-24858) * bpo-43428: Sync with importlib_metadata 3.7.3 (16ac3a95) * Add 'versionadded' for importlib.metadata.packages_distributions * Add section in what's new for Python 3.10 highlighting most salient changes and relevant backport. --- Doc/library/importlib.metadata.rst | 43 +++++++++++++++++++++++++++++++++++++- Doc/whatsnew/3.10.rst | 13 ++++++++++++ Lib/importlib/metadata.py | 20 ++++++++++++++++-- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index ffce1ba..fee5e67 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -79,15 +79,43 @@ 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. + +Query all entry points:: >>> eps = entry_points() # doctest: +SKIP + +The ``entry_points()`` function returns an ``EntryPoints`` object, +a sequence of all ``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 +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 +through to select:: + + >>> scripts = entry_points(group='console_scripts') # doctest: +SKIP + +Pick out a specific script named "wheel" (found in the wheel project):: + >>> 'wheel' in scripts.names # doctest: +SKIP True >>> wheel = scripts['wheel'] # doctest: +SKIP + +Equivalently, query for that entry point during selection:: + + >>> (wheel,) = entry_points(group='console_scripts', name='wheel') # doctest: +SKIP + >>> (wheel,) = entry_points().select(group='console_scripts', name='wheel') # doctest: +SKIP + +Inspect the resolved entry point:: + >>> wheel # doctest: +SKIP EntryPoint(name='wheel', value='wheel.cli:main', group='console_scripts') >>> wheel.module # doctest: +SKIP @@ -106,6 +134,17 @@ group. Read `the setuptools docs `_ for more information on entry points, their definition, and usage. +*Compatibility Note* + +The "selectable" entry points were introduced in ``importlib_metadata`` +3.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted +no parameters and always returned a dictionary of entry points, keyed +by group. For compatibility, if no parameters are passed to entry_points, +a ``SelectableGroups`` object is returned, implementing that dict +interface. In the future, calling ``entry_points`` with no parameters +will return an ``EntryPoints`` object. Users should rely on the selection +interface to retrieve entry points by group. + .. _metadata: @@ -199,6 +238,8 @@ Python packages or modules:: >>> packages_distributions() {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...} +.. versionadded:: 3.10 + Distributions ============= diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index db36f69..c4c282e 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -690,6 +690,19 @@ Added the *root_dir* and *dir_fd* parameters in :func:`~glob.glob` and :func:`~glob.iglob` which allow to specify the root directory for searching. (Contributed by Serhiy Storchaka in :issue:`38144`.) +importlib.metadata +------------------ + +Feature parity with ``importlib_metadata`` 3.7. + +:func:`importlib.metadata.entry_points` now provides a nicer experience +for selecting entry points by group and name through a new +:class:`importlib.metadata.EntryPoints` class. + +Added :func:`importlib.metadata.packages_distributions` for resolving +top-level Python modules and packages to their +:class:`importlib.metadata.Distribution`. + inspect ------- diff --git a/Lib/importlib/metadata.py b/Lib/importlib/metadata.py index 8a73185..53c1a14 100644 --- a/Lib/importlib/metadata.py +++ b/Lib/importlib/metadata.py @@ -4,7 +4,6 @@ import abc import csv import sys import email -import inspect import pathlib import zipfile import operator @@ -12,7 +11,7 @@ import warnings import functools import itertools import posixpath -import collections.abc +import collections from ._itertools import unique_everseen @@ -33,6 +32,7 @@ __all__ = [ 'entry_points', 'files', 'metadata', + 'packages_distributions', 'requires', 'version', ] @@ -158,21 +158,33 @@ class EntryPoints(tuple): __slots__ = () def __getitem__(self, name): # -> EntryPoint: + """ + Get the EntryPoint in self matching name. + """ try: return next(iter(self.select(name=name))) except StopIteration: raise KeyError(name) def select(self, **params): + """ + Select entry points from self that match the + given parameters (typically group and/or name). + """ return EntryPoints(ep for ep in self if ep.matches(**params)) @property def names(self): + """ + Return the set of all names of all entry points. + """ return set(ep.name for ep in self) @property def groups(self): """ + Return the set of all groups of all entry points. + For coverage while SelectableGroups is present. >>> EntryPoints().groups set() @@ -185,6 +197,9 @@ class EntryPoints(tuple): def flake8_bypass(func): + # defer inspect import as performance optimization. + import inspect + is_flake8 = any('flake8' in str(frame.filename) for frame in inspect.stack()[:5]) return func if not is_flake8 else lambda: None @@ -813,6 +828,7 @@ def packages_distributions() -> Mapping[str, List[str]]: Return a mapping of top-level packages to their distributions. + >>> import collections.abc >>> pkgs = packages_distributions() >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values()) True -- cgit v0.12