Skip to content

bpo-43428: Improve documentation for importlib.metadata changes. #24858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 15, 2021
Merged
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
43 changes: 42 additions & 1 deletion Doc/library/importlib.metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -106,6 +134,17 @@ group. Read `the setuptools docs
<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
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:

Expand Down Expand Up @@ -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
=============
Expand Down
13 changes: 13 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------

Expand Down
20 changes: 18 additions & 2 deletions Lib/importlib/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import csv
import sys
import email
import inspect
import pathlib
import zipfile
import operator
import warnings
import functools
import itertools
import posixpath
import collections.abc
import collections

from ._itertools import unique_everseen

Expand All @@ -33,6 +32,7 @@
'entry_points',
'files',
'metadata',
'packages_distributions',
'requires',
'version',
]
Expand Down Expand Up @@ -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()
Expand All @@ -185,6 +197,9 @@ def _from_text_for(cls, text, dist):


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

Expand Down Expand Up @@ -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
Expand Down