Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion Lib/importlib/metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,100 @@ def matches(self, **params):
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.
"""
Expand All @@ -215,6 +308,14 @@ def __getitem__(self, name): # -> EntryPoint:
"""
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:
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_importlib/test_metadata_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ def test_entry_points_dict_construction(self):
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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]``).