From c34ed08d975fb7daa7b329f7c631647782290393 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 31 May 2021 12:19:42 -0400 Subject: bpo-44246: Restore compatibility in entry_points (GH-26468) * bpo-44246: Entry points performance improvements. From importlib_metadata 4.3.1. * bpo-44246: Sync with importlib_metadata 4.4 --- Lib/importlib/metadata/__init__.py | 103 ++++++++++++++++++++- Lib/test/test_importlib/test_metadata_api.py | 16 ++++ .../2021-05-31-11-34-56.bpo-44246.yHAkF0.rst | 7 ++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2021-05-31-11-34-56.bpo-44246.yHAkF0.rst diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index 2e3403e..d2116cf 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -204,7 +204,100 @@ class EntryPoint( return all(map(operator.eq, params.values(), attrs)) -class EntryPoints(tuple): +class DeprecatedList(list): + """ + 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 + """ + + _warn = functools.partial( + warnings.warn, + "EntryPoints list interface is deprecated. Cast to list if needed.", + DeprecationWarning, + stacklevel=2, + ) + + def __setitem__(self, *args, **kwargs): + self._warn() + return super().__setitem__(*args, **kwargs) + + def __delitem__(self, *args, **kwargs): + self._warn() + return super().__delitem__(*args, **kwargs) + + def append(self, *args, **kwargs): + self._warn() + return super().append(*args, **kwargs) + + def reverse(self, *args, **kwargs): + self._warn() + return super().reverse(*args, **kwargs) + + def extend(self, *args, **kwargs): + self._warn() + return super().extend(*args, **kwargs) + + def pop(self, *args, **kwargs): + self._warn() + return super().pop(*args, **kwargs) + + def remove(self, *args, **kwargs): + self._warn() + return super().remove(*args, **kwargs) + + def __iadd__(self, *args, **kwargs): + self._warn() + return super().__iadd__(*args, **kwargs) + + def __add__(self, other): + if not isinstance(other, tuple): + self._warn() + other = tuple(other) + return self.__class__(tuple(self) + other) + + def insert(self, *args, **kwargs): + self._warn() + return super().insert(*args, **kwargs) + + def sort(self, *args, **kwargs): + self._warn() + return super().sort(*args, **kwargs) + + 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. """ @@ -215,6 +308,14 @@ class EntryPoints(tuple): """ 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: return next(iter(self.select(name=name))) except StopIteration: diff --git a/Lib/test/test_importlib/test_metadata_api.py b/Lib/test/test_importlib/test_metadata_api.py index 825edc1..3506493 100644 --- a/Lib/test/test_importlib/test_metadata_api.py +++ b/Lib/test/test_importlib/test_metadata_api.py @@ -130,6 +130,22 @@ class APITests( 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 warnings.catch_warnings(record=True) 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 diff --git a/Misc/NEWS.d/next/Library/2021-05-31-11-34-56.bpo-44246.yHAkF0.rst b/Misc/NEWS.d/next/Library/2021-05-31-11-34-56.bpo-44246.yHAkF0.rst new file mode 100644 index 0000000..b93f8b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-05-31-11-34-56.bpo-44246.yHAkF0.rst @@ -0,0 +1,7 @@ +In ``importlib.metadata``, restore compatibility in the result from +``Distribution.entry_points`` (``EntryPoints``) to honor expectations in +older implementations and issuing deprecation warnings for these cases: A. ``EntryPoints`` objects are once again mutable, allowing for ``sort()`` +and other list-based mutation operations. Avoid deprecation warnings by +casting to a mutable sequence (e.g. ``list(dist.entry_points).sort()``). B. ``EntryPoints`` results once again allow for access by index. To avoid +deprecation warnings, cast the result to a Sequence first (e.g. +``tuple(dist.entry_points)[0]``). -- cgit v0.12