mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	gh-97781: Apply changes from importlib_metadata 5. (GH-97785)
* gh-97781: Apply changes from importlib_metadata 5. * Apply changes from upstream * Apply changes from upstream.
This commit is contained in:
		
							parent
							
								
									2b5f1360ea
								
							
						
					
					
						commit
						8af04cdef2
					
				
					 5 changed files with 88 additions and 302 deletions
				
			
		|  | @ -13,21 +13,39 @@ | ||||||
| 
 | 
 | ||||||
| **Source code:** :source:`Lib/importlib/metadata/__init__.py` | **Source code:** :source:`Lib/importlib/metadata/__init__.py` | ||||||
| 
 | 
 | ||||||
| ``importlib.metadata`` is a library that provides access to installed | ``importlib_metadata`` is a library that provides access to | ||||||
| package metadata, such as its entry points or its | the metadata of an installed `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_, | ||||||
| top-level name.  Built in part on Python's import system, this library | such as its entry points | ||||||
|  | or its top-level names (`Import Package <https://packaging.python.org/en/latest/glossary/#term-Import-Package>`_\s, modules, if any). | ||||||
|  | Built in part on Python's import system, this library | ||||||
| intends to replace similar functionality in the `entry point | intends to replace similar functionality in the `entry point | ||||||
| API`_ and `metadata API`_ of ``pkg_resources``.  Along with | API`_ and `metadata API`_ of ``pkg_resources``.  Along with | ||||||
| :mod:`importlib.resources`, | :mod:`importlib.resources`, | ||||||
| this package can eliminate the need to use the older and less efficient | this package can eliminate the need to use the older and less efficient | ||||||
| ``pkg_resources`` package. | ``pkg_resources`` package. | ||||||
| 
 | 
 | ||||||
| By "installed package" we generally mean a third-party package installed into | ``importlib_metadata`` operates on third-party *distribution packages* | ||||||
| Python's ``site-packages`` directory via tools such as `pip | installed into Python's ``site-packages`` directory via tools such as | ||||||
| <https://pypi.org/project/pip/>`_.  Specifically, | `pip <https://pypi.org/project/pip/>`_. | ||||||
| it means a package with either a discoverable ``dist-info`` or ``egg-info`` | Specifically, it works with distributions with discoverable | ||||||
| directory, and metadata defined by :pep:`566` or its older specifications. | ``dist-info`` or ``egg-info`` directories, | ||||||
| By default, package metadata can live on the file system or in zip archives on | and metadata defined by the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_. | ||||||
|  | 
 | ||||||
|  | .. important:: | ||||||
|  | 
 | ||||||
|  |    These are *not* necessarily equivalent to or correspond 1:1 with | ||||||
|  |    the top-level *import package* names | ||||||
|  |    that can be imported inside Python code. | ||||||
|  |    One *distribution package* can contain multiple *import packages* | ||||||
|  |    (and single modules), | ||||||
|  |    and one top-level *import package* | ||||||
|  |    may map to multiple *distribution packages* | ||||||
|  |    if it is a namespace package. | ||||||
|  |    You can use :ref:`package_distributions() <package-distributions>` | ||||||
|  |    to get a mapping between them. | ||||||
|  | 
 | ||||||
|  | By default, distribution metadata can live on the file system | ||||||
|  | or in zip archives on | ||||||
| :data:`sys.path`.  Through an extension mechanism, the metadata can live almost | :data:`sys.path`.  Through an extension mechanism, the metadata can live almost | ||||||
| anywhere. | anywhere. | ||||||
| 
 | 
 | ||||||
|  | @ -37,12 +55,19 @@ anywhere. | ||||||
|    https://importlib-metadata.readthedocs.io/ |    https://importlib-metadata.readthedocs.io/ | ||||||
|       The documentation for ``importlib_metadata``, which supplies a |       The documentation for ``importlib_metadata``, which supplies a | ||||||
|       backport of ``importlib.metadata``. |       backport of ``importlib.metadata``. | ||||||
|  |       This includes an `API reference | ||||||
|  |       <https://importlib-metadata.readthedocs.io/en/latest/api.html>`__ | ||||||
|  |       for this module's classes and functions, | ||||||
|  |       as well as a `migration guide | ||||||
|  |       <https://importlib-metadata.readthedocs.io/en/latest/migration.html>`__ | ||||||
|  |       for existing users of ``pkg_resources``. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Overview | Overview | ||||||
| ======== | ======== | ||||||
| 
 | 
 | ||||||
| Let's say you wanted to get the version string for a package you've installed | Let's say you wanted to get the version string for a | ||||||
|  | `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ you've installed | ||||||
| using ``pip``.  We start by creating a virtual environment and installing | using ``pip``.  We start by creating a virtual environment and installing | ||||||
| something into it: | something into it: | ||||||
| 
 | 
 | ||||||
|  | @ -151,11 +176,10 @@ for more information on entry points, their definition, and usage. | ||||||
| The "selectable" entry points were introduced in ``importlib_metadata`` | The "selectable" entry points were introduced in ``importlib_metadata`` | ||||||
| 3.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted | 3.6 and Python 3.10. Prior to those changes, ``entry_points`` accepted | ||||||
| no parameters and always returned a dictionary of entry points, keyed | no parameters and always returned a dictionary of entry points, keyed | ||||||
| by group. For compatibility, if no parameters are passed to entry_points, | by group. With ``importlib_metadata`` 5.0 and Python 3.12, | ||||||
| a ``SelectableGroups`` object is returned, implementing that dict | ``entry_points`` always returns an ``EntryPoints`` object. See | ||||||
| interface. In the future, calling ``entry_points`` with no parameters | `backports.entry_points_selectable <https://pypi.org/project/backports.entry_points_selectable>`_ | ||||||
| will return an ``EntryPoints`` object. Users should rely on the selection | for compatibility options. | ||||||
| interface to retrieve entry points by group. |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .. _metadata: | .. _metadata: | ||||||
|  | @ -163,7 +187,8 @@ interface to retrieve entry points by group. | ||||||
| Distribution metadata | Distribution metadata | ||||||
| --------------------- | --------------------- | ||||||
| 
 | 
 | ||||||
| Every distribution includes some metadata, which you can extract using the | Every `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ includes some metadata, | ||||||
|  | which you can extract using the | ||||||
| ``metadata()`` function:: | ``metadata()`` function:: | ||||||
| 
 | 
 | ||||||
|     >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP |     >>> wheel_metadata = metadata('wheel')  # doctest: +SKIP | ||||||
|  | @ -201,7 +226,8 @@ all the metadata in a JSON-compatible form per :PEP:`566`:: | ||||||
| Distribution versions | Distribution versions | ||||||
| --------------------- | --------------------- | ||||||
| 
 | 
 | ||||||
| The ``version()`` function is the quickest way to get a distribution's version | The ``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:: | number, as a string:: | ||||||
| 
 | 
 | ||||||
|     >>> version('wheel')  # doctest: +SKIP |     >>> version('wheel')  # doctest: +SKIP | ||||||
|  | @ -214,7 +240,8 @@ Distribution files | ||||||
| ------------------ | ------------------ | ||||||
| 
 | 
 | ||||||
| You can also get the full set of files contained within a distribution.  The | 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 | ``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 | files installed by this distribution.  Each file object returned is a | ||||||
| ``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``, | ``PackagePath``, a :class:`pathlib.PurePath` derived object with additional ``dist``, | ||||||
| ``size``, and ``hash`` properties as indicated by the metadata.  For example:: | ``size``, and ``hash`` properties as indicated by the metadata.  For example:: | ||||||
|  | @ -259,19 +286,24 @@ distribution is not known to have the metadata present. | ||||||
| Distribution requirements | Distribution requirements | ||||||
| ------------------------- | ------------------------- | ||||||
| 
 | 
 | ||||||
| To get the full set of requirements for a distribution, use the ``requires()`` | To get the full set of requirements for a `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_, | ||||||
|  | use the ``requires()`` | ||||||
| function:: | function:: | ||||||
| 
 | 
 | ||||||
|     >>> requires('wheel')  # doctest: +SKIP |     >>> requires('wheel')  # doctest: +SKIP | ||||||
|     ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] |     ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Package distributions | .. _package-distributions: | ||||||
| --------------------- | .. _import-distribution-package-mapping: | ||||||
| 
 | 
 | ||||||
| A convenience method to resolve the distribution or | Mapping import to distribution packages | ||||||
| distributions (in the case of a namespace package) for top-level | --------------------------------------- | ||||||
| Python packages or modules:: | 
 | ||||||
|  | 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 | ||||||
|  | Python module or `Import Package <https://packaging.python.org/en/latest/glossary/#term-Import-Package>`_:: | ||||||
| 
 | 
 | ||||||
|     >>> packages_distributions() |     >>> packages_distributions() | ||||||
|     {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...} |     {'importlib_metadata': ['importlib-metadata'], 'yaml': ['PyYAML'], 'jaraco': ['jaraco.classes', 'jaraco.functools'], ...} | ||||||
|  | @ -285,7 +317,8 @@ Distributions | ||||||
| 
 | 
 | ||||||
| While the above API is the most common and convenient usage, you can get all | 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 | of that information from the ``Distribution`` class.  A ``Distribution`` is an | ||||||
| abstract object that represents the metadata for a Python package.  You can | 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:: | get the ``Distribution`` instance:: | ||||||
| 
 | 
 | ||||||
|     >>> from importlib.metadata import distribution  # doctest: +SKIP |     >>> from importlib.metadata import distribution  # doctest: +SKIP | ||||||
|  | @ -305,14 +338,16 @@ instance:: | ||||||
|     >>> dist.metadata['License']  # doctest: +SKIP |     >>> dist.metadata['License']  # doctest: +SKIP | ||||||
|     'MIT' |     'MIT' | ||||||
| 
 | 
 | ||||||
| The full set of available metadata is not described here.  See :pep:`566` | The full set of available metadata is not described here. | ||||||
| for additional details. | See the `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_ for additional details. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Distribution Discovery | Distribution Discovery | ||||||
| ====================== | ====================== | ||||||
| 
 | 
 | ||||||
| By default, this package provides built-in support for discovery of metadata for file system and zip file packages. This metadata finder search defaults to ``sys.path``, but varies slightly in how it interprets those values from how other import machinery does. In particular: | By default, this package provides built-in support for discovery of metadata | ||||||
|  | for file system and zip file `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_\s. | ||||||
|  | This metadata finder search defaults to ``sys.path``, but varies slightly in how it interprets those values from how other import machinery does. In particular: | ||||||
| 
 | 
 | ||||||
| - ``importlib.metadata`` does not honor :class:`bytes` objects on ``sys.path``. | - ``importlib.metadata`` does not honor :class:`bytes` objects on ``sys.path``. | ||||||
| - ``importlib.metadata`` will incidentally honor :py:class:`pathlib.Path` objects on ``sys.path`` even though such values will be ignored for imports. | - ``importlib.metadata`` will incidentally honor :py:class:`pathlib.Path` objects on ``sys.path`` even though such values will be ignored for imports. | ||||||
|  | @ -321,15 +356,18 @@ By default, this package provides built-in support for discovery of metadata for | ||||||
| Extending the search algorithm | Extending the search algorithm | ||||||
| ============================== | ============================== | ||||||
| 
 | 
 | ||||||
| Because package metadata is not available through :data:`sys.path` searches, or | Because `Distribution Package <https://packaging.python.org/en/latest/glossary/#term-Distribution-Package>`_ metadata | ||||||
| package loaders directly, the metadata for a package is found through import | is not available through :data:`sys.path` searches, or | ||||||
| system :ref:`finders <finders-and-loaders>`.  To find a distribution package's metadata, | package loaders directly, | ||||||
|  | the metadata for a distribution is found through import | ||||||
|  | system `finders`_.  To find a distribution package's metadata, | ||||||
| ``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on | ``importlib.metadata`` queries the list of :term:`meta path finders <meta path finder>` on | ||||||
| :data:`sys.meta_path`. | :data:`sys.meta_path`. | ||||||
| 
 | 
 | ||||||
| The default ``PathFinder`` for Python includes a hook that calls into | By default ``importlib_metadata`` installs a finder for distribution packages | ||||||
| ``importlib.metadata.MetadataPathFinder`` for finding distributions | found on the file system. | ||||||
| loaded from typical file-system-based paths. | This finder doesn't actually find any *distributions*, | ||||||
|  | but it can find their metadata. | ||||||
| 
 | 
 | ||||||
| The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the | The abstract class :py:class:`importlib.abc.MetaPathFinder` defines the | ||||||
| interface expected of finders by Python's import system. | interface expected of finders by Python's import system. | ||||||
|  | @ -358,4 +396,4 @@ a custom finder, return instances of this derived ``Distribution`` in the | ||||||
| 
 | 
 | ||||||
| .. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points | .. _`entry point API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points | ||||||
| .. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api | .. _`metadata API`: https://setuptools.readthedocs.io/en/latest/pkg_resources.html#metadata-api | ||||||
| .. _`importlib_resources`: https://importlib-resources.readthedocs.io/en/latest/index.html | .. _`finders`: https://docs.python.org/3/reference/import.html#finders-and-loaders | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ | ||||||
| from importlib import import_module | from importlib import import_module | ||||||
| from importlib.abc import MetaPathFinder | from importlib.abc import MetaPathFinder | ||||||
| from itertools import starmap | from itertools import starmap | ||||||
| from typing import List, Mapping, Optional, Union | from typing import List, Mapping, Optional | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| __all__ = [ | __all__ = [ | ||||||
|  | @ -134,6 +134,7 @@ class DeprecatedTuple: | ||||||
|     1 |     1 | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|  |     # Do not remove prior to 2023-05-01 or Python 3.13 | ||||||
|     _warn = functools.partial( |     _warn = functools.partial( | ||||||
|         warnings.warn, |         warnings.warn, | ||||||
|         "EntryPoint tuple interface is deprecated. Access members by name.", |         "EntryPoint tuple interface is deprecated. Access members by name.", | ||||||
|  | @ -184,6 +185,10 @@ class EntryPoint(DeprecatedTuple): | ||||||
|     following the attr, and following any extras. |     following the attr, and following any extras. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|  |     name: str | ||||||
|  |     value: str | ||||||
|  |     group: str | ||||||
|  | 
 | ||||||
|     dist: Optional['Distribution'] = None |     dist: Optional['Distribution'] = None | ||||||
| 
 | 
 | ||||||
|     def __init__(self, name, value, group): |     def __init__(self, name, value, group): | ||||||
|  | @ -218,17 +223,6 @@ def _for(self, dist): | ||||||
|         vars(self).update(dist=dist) |         vars(self).update(dist=dist) | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|     def __iter__(self): |  | ||||||
|         """ |  | ||||||
|         Supply iter so one may construct dicts of EntryPoints by name. |  | ||||||
|         """ |  | ||||||
|         msg = ( |  | ||||||
|             "Construction of dict of EntryPoints is deprecated in " |  | ||||||
|             "favor of EntryPoints." |  | ||||||
|         ) |  | ||||||
|         warnings.warn(msg, DeprecationWarning) |  | ||||||
|         return iter((self.name, self)) |  | ||||||
| 
 |  | ||||||
|     def matches(self, **params): |     def matches(self, **params): | ||||||
|         """ |         """ | ||||||
|         EntryPoint matches the given parameters. |         EntryPoint matches the given parameters. | ||||||
|  | @ -274,77 +268,7 @@ def __hash__(self): | ||||||
|         return hash(self._key()) |         return hash(self._key()) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DeprecatedList(list): | class EntryPoints(tuple): | ||||||
|     """ |  | ||||||
|     Allow an otherwise immutable object to implement mutability |  | ||||||
|     for compatibility. |  | ||||||
| 
 |  | ||||||
|     >>> recwarn = getfixture('recwarn') |  | ||||||
|     >>> dl = DeprecatedList(range(3)) |  | ||||||
|     >>> dl[0] = 1 |  | ||||||
|     >>> dl.append(3) |  | ||||||
|     >>> del dl[3] |  | ||||||
|     >>> dl.reverse() |  | ||||||
|     >>> dl.sort() |  | ||||||
|     >>> dl.extend([4]) |  | ||||||
|     >>> dl.pop(-1) |  | ||||||
|     4 |  | ||||||
|     >>> dl.remove(1) |  | ||||||
|     >>> dl += [5] |  | ||||||
|     >>> dl + [6] |  | ||||||
|     [1, 2, 5, 6] |  | ||||||
|     >>> dl + (6,) |  | ||||||
|     [1, 2, 5, 6] |  | ||||||
|     >>> dl.insert(0, 0) |  | ||||||
|     >>> dl |  | ||||||
|     [0, 1, 2, 5] |  | ||||||
|     >>> dl == [0, 1, 2, 5] |  | ||||||
|     True |  | ||||||
|     >>> dl == (0, 1, 2, 5) |  | ||||||
|     True |  | ||||||
|     >>> len(recwarn) |  | ||||||
|     1 |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     __slots__ = () |  | ||||||
| 
 |  | ||||||
|     _warn = functools.partial( |  | ||||||
|         warnings.warn, |  | ||||||
|         "EntryPoints list interface is deprecated. Cast to list if needed.", |  | ||||||
|         DeprecationWarning, |  | ||||||
|         stacklevel=2, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     def _wrap_deprecated_method(method_name: str):  # type: ignore |  | ||||||
|         def wrapped(self, *args, **kwargs): |  | ||||||
|             self._warn() |  | ||||||
|             return getattr(super(), method_name)(*args, **kwargs) |  | ||||||
| 
 |  | ||||||
|         return method_name, wrapped |  | ||||||
| 
 |  | ||||||
|     locals().update( |  | ||||||
|         map( |  | ||||||
|             _wrap_deprecated_method, |  | ||||||
|             '__setitem__ __delitem__ append reverse extend pop remove ' |  | ||||||
|             '__iadd__ insert sort'.split(), |  | ||||||
|         ) |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     def __add__(self, other): |  | ||||||
|         if not isinstance(other, tuple): |  | ||||||
|             self._warn() |  | ||||||
|             other = tuple(other) |  | ||||||
|         return self.__class__(tuple(self) + other) |  | ||||||
| 
 |  | ||||||
|     def __eq__(self, other): |  | ||||||
|         if not isinstance(other, tuple): |  | ||||||
|             self._warn() |  | ||||||
|             other = tuple(other) |  | ||||||
| 
 |  | ||||||
|         return tuple(self).__eq__(other) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class EntryPoints(DeprecatedList): |  | ||||||
|     """ |     """ | ||||||
|     An immutable collection of selectable EntryPoint objects. |     An immutable collection of selectable EntryPoint objects. | ||||||
|     """ |     """ | ||||||
|  | @ -355,14 +279,6 @@ def __getitem__(self, name):  # -> EntryPoint: | ||||||
|         """ |         """ | ||||||
|         Get the EntryPoint in self matching name. |         Get the EntryPoint in self matching name. | ||||||
|         """ |         """ | ||||||
|         if isinstance(name, int): |  | ||||||
|             warnings.warn( |  | ||||||
|                 "Accessing entry points by index is deprecated. " |  | ||||||
|                 "Cast to tuple if needed.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|                 stacklevel=2, |  | ||||||
|             ) |  | ||||||
|             return super().__getitem__(name) |  | ||||||
|         try: |         try: | ||||||
|             return next(iter(self.select(name=name))) |             return next(iter(self.select(name=name))) | ||||||
|         except StopIteration: |         except StopIteration: | ||||||
|  | @ -386,10 +302,6 @@ def names(self): | ||||||
|     def groups(self): |     def groups(self): | ||||||
|         """ |         """ | ||||||
|         Return the set of all groups of all entry points. |         Return the set of all groups of all entry points. | ||||||
| 
 |  | ||||||
|         For coverage while SelectableGroups is present. |  | ||||||
|         >>> EntryPoints().groups |  | ||||||
|         set() |  | ||||||
|         """ |         """ | ||||||
|         return {ep.group for ep in self} |         return {ep.group for ep in self} | ||||||
| 
 | 
 | ||||||
|  | @ -405,101 +317,6 @@ def _from_text(text): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Deprecated: |  | ||||||
|     """ |  | ||||||
|     Compatibility add-in for mapping to indicate that |  | ||||||
|     mapping behavior is deprecated. |  | ||||||
| 
 |  | ||||||
|     >>> recwarn = getfixture('recwarn') |  | ||||||
|     >>> class DeprecatedDict(Deprecated, dict): pass |  | ||||||
|     >>> dd = DeprecatedDict(foo='bar') |  | ||||||
|     >>> dd.get('baz', None) |  | ||||||
|     >>> dd['foo'] |  | ||||||
|     'bar' |  | ||||||
|     >>> list(dd) |  | ||||||
|     ['foo'] |  | ||||||
|     >>> list(dd.keys()) |  | ||||||
|     ['foo'] |  | ||||||
|     >>> 'foo' in dd |  | ||||||
|     True |  | ||||||
|     >>> list(dd.values()) |  | ||||||
|     ['bar'] |  | ||||||
|     >>> len(recwarn) |  | ||||||
|     1 |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     _warn = functools.partial( |  | ||||||
|         warnings.warn, |  | ||||||
|         "SelectableGroups dict interface is deprecated. Use select.", |  | ||||||
|         DeprecationWarning, |  | ||||||
|         stacklevel=2, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     def __getitem__(self, name): |  | ||||||
|         self._warn() |  | ||||||
|         return super().__getitem__(name) |  | ||||||
| 
 |  | ||||||
|     def get(self, name, default=None): |  | ||||||
|         self._warn() |  | ||||||
|         return super().get(name, default) |  | ||||||
| 
 |  | ||||||
|     def __iter__(self): |  | ||||||
|         self._warn() |  | ||||||
|         return super().__iter__() |  | ||||||
| 
 |  | ||||||
|     def __contains__(self, *args): |  | ||||||
|         self._warn() |  | ||||||
|         return super().__contains__(*args) |  | ||||||
| 
 |  | ||||||
|     def keys(self): |  | ||||||
|         self._warn() |  | ||||||
|         return super().keys() |  | ||||||
| 
 |  | ||||||
|     def values(self): |  | ||||||
|         self._warn() |  | ||||||
|         return super().values() |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class SelectableGroups(Deprecated, dict): |  | ||||||
|     """ |  | ||||||
|     A backward- and forward-compatible result from |  | ||||||
|     entry_points that fully implements the dict interface. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def load(cls, eps): |  | ||||||
|         by_group = operator.attrgetter('group') |  | ||||||
|         ordered = sorted(eps, key=by_group) |  | ||||||
|         grouped = itertools.groupby(ordered, by_group) |  | ||||||
|         return cls((group, EntryPoints(eps)) for group, eps in grouped) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def _all(self): |  | ||||||
|         """ |  | ||||||
|         Reconstruct a list of all entrypoints from the groups. |  | ||||||
|         """ |  | ||||||
|         groups = super(Deprecated, self).values() |  | ||||||
|         return EntryPoints(itertools.chain.from_iterable(groups)) |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def groups(self): |  | ||||||
|         return self._all.groups |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def names(self): |  | ||||||
|         """ |  | ||||||
|         for coverage: |  | ||||||
|         >>> SelectableGroups().names |  | ||||||
|         set() |  | ||||||
|         """ |  | ||||||
|         return self._all.names |  | ||||||
| 
 |  | ||||||
|     def select(self, **params): |  | ||||||
|         if not params: |  | ||||||
|             return self |  | ||||||
|         return self._all.select(**params) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PackagePath(pathlib.PurePosixPath): | class PackagePath(pathlib.PurePosixPath): | ||||||
|     """A reference to a path in a package""" |     """A reference to a path in a package""" | ||||||
| 
 | 
 | ||||||
|  | @ -1013,27 +830,19 @@ def version(distribution_name): | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def entry_points(**params) -> Union[EntryPoints, SelectableGroups]: | def entry_points(**params) -> EntryPoints: | ||||||
|     """Return EntryPoint objects for all installed packages. |     """Return EntryPoint objects for all installed packages. | ||||||
| 
 | 
 | ||||||
|     Pass selection parameters (group or name) to filter the |     Pass selection parameters (group or name) to filter the | ||||||
|     result to entry points matching those properties (see |     result to entry points matching those properties (see | ||||||
|     EntryPoints.select()). |     EntryPoints.select()). | ||||||
| 
 | 
 | ||||||
|     For compatibility, returns ``SelectableGroups`` object unless |     :return: EntryPoints for all installed packages. | ||||||
|     selection parameters are supplied. In the future, this function |  | ||||||
|     will return ``EntryPoints`` instead of ``SelectableGroups`` |  | ||||||
|     even when no selection parameters are supplied. |  | ||||||
| 
 |  | ||||||
|     For maximum future compatibility, pass selection parameters |  | ||||||
|     or invoke ``.select`` with parameters on the result. |  | ||||||
| 
 |  | ||||||
|     :return: EntryPoints or SelectableGroups for all installed packages. |  | ||||||
|     """ |     """ | ||||||
|     eps = itertools.chain.from_iterable( |     eps = itertools.chain.from_iterable( | ||||||
|         dist.entry_points for dist in _unique(distributions()) |         dist.entry_points for dist in _unique(distributions()) | ||||||
|     ) |     ) | ||||||
|     return SelectableGroups.load(eps).select(**params) |     return EntryPoints(eps).select(**params) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def files(distribution_name): | def files(distribution_name): | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| import re | import re | ||||||
| import json |  | ||||||
| import pickle | import pickle | ||||||
| import unittest | import unittest | ||||||
| import warnings |  | ||||||
| import importlib.metadata | import importlib.metadata | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|  | @ -260,14 +258,6 @@ def test_hashable(self): | ||||||
|         """EntryPoints should be hashable""" |         """EntryPoints should be hashable""" | ||||||
|         hash(self.ep) |         hash(self.ep) | ||||||
| 
 | 
 | ||||||
|     def test_json_dump(self): |  | ||||||
|         """ |  | ||||||
|         json should not expect to be able to dump an EntryPoint |  | ||||||
|         """ |  | ||||||
|         with self.assertRaises(Exception): |  | ||||||
|             with warnings.catch_warnings(record=True): |  | ||||||
|                 json.dumps(self.ep) |  | ||||||
| 
 |  | ||||||
|     def test_module(self): |     def test_module(self): | ||||||
|         assert self.ep.module == 'value' |         assert self.ep.module == 'value' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -124,62 +124,6 @@ def test_entry_points_missing_name(self): | ||||||
|     def test_entry_points_missing_group(self): |     def test_entry_points_missing_group(self): | ||||||
|         assert entry_points(group='missing') == () |         assert entry_points(group='missing') == () | ||||||
| 
 | 
 | ||||||
|     def test_entry_points_dict_construction(self): |  | ||||||
|         """ |  | ||||||
|         Prior versions of entry_points() returned simple lists and |  | ||||||
|         allowed casting those lists into maps by name using ``dict()``. |  | ||||||
|         Capture this now deprecated use-case. |  | ||||||
|         """ |  | ||||||
|         with suppress_known_deprecation() as caught: |  | ||||||
|             eps = dict(entry_points(group='entries')) |  | ||||||
| 
 |  | ||||||
|         assert 'main' in eps |  | ||||||
|         assert eps['main'] == entry_points(group='entries')['main'] |  | ||||||
| 
 |  | ||||||
|         # check warning |  | ||||||
|         expected = next(iter(caught)) |  | ||||||
|         assert expected.category is DeprecationWarning |  | ||||||
|         assert "Construction of dict of EntryPoints is deprecated" in str(expected) |  | ||||||
| 
 |  | ||||||
|     def test_entry_points_by_index(self): |  | ||||||
|         """ |  | ||||||
|         Prior versions of Distribution.entry_points would return a |  | ||||||
|         tuple that allowed access by index. |  | ||||||
|         Capture this now deprecated use-case |  | ||||||
|         See python/importlib_metadata#300 and bpo-44246. |  | ||||||
|         """ |  | ||||||
|         eps = distribution('distinfo-pkg').entry_points |  | ||||||
|         with suppress_known_deprecation() as caught: |  | ||||||
|             eps[0] |  | ||||||
| 
 |  | ||||||
|         # check warning |  | ||||||
|         expected = next(iter(caught)) |  | ||||||
|         assert expected.category is DeprecationWarning |  | ||||||
|         assert "Accessing entry points by index is deprecated" in str(expected) |  | ||||||
| 
 |  | ||||||
|     def test_entry_points_groups_getitem(self): |  | ||||||
|         """ |  | ||||||
|         Prior versions of entry_points() returned a dict. Ensure |  | ||||||
|         that callers using '.__getitem__()' are supported but warned to |  | ||||||
|         migrate. |  | ||||||
|         """ |  | ||||||
|         with suppress_known_deprecation(): |  | ||||||
|             entry_points()['entries'] == entry_points(group='entries') |  | ||||||
| 
 |  | ||||||
|             with self.assertRaises(KeyError): |  | ||||||
|                 entry_points()['missing'] |  | ||||||
| 
 |  | ||||||
|     def test_entry_points_groups_get(self): |  | ||||||
|         """ |  | ||||||
|         Prior versions of entry_points() returned a dict. Ensure |  | ||||||
|         that callers using '.get()' are supported but warned to |  | ||||||
|         migrate. |  | ||||||
|         """ |  | ||||||
|         with suppress_known_deprecation(): |  | ||||||
|             entry_points().get('missing', 'default') == 'default' |  | ||||||
|             entry_points().get('entries', 'default') == entry_points()['entries'] |  | ||||||
|             entry_points().get('missing', ()) == () |  | ||||||
| 
 |  | ||||||
|     def test_entry_points_allows_no_attributes(self): |     def test_entry_points_allows_no_attributes(self): | ||||||
|         ep = entry_points().select(group='entries', name='main') |         ep = entry_points().select(group='entries', name='main') | ||||||
|         with self.assertRaises(AttributeError): |         with self.assertRaises(AttributeError): | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | Removed deprecated interfaces in ``importlib.metadata`` (entry points | ||||||
|  | accessed as dictionary, implicit dictionary construction of sequence of | ||||||
|  | ``EntryPoint`` objects, mutablility of ``EntryPoints`` result, access of | ||||||
|  | entry point by index). ``entry_points`` now has a simpler, more | ||||||
|  | straightforward API (returning ``EntryPoints``). | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jason R. Coombs
						Jason R. Coombs