Skip to content

Commit

Permalink
Merge branch 'main' into feature/uniform
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed May 31, 2021
2 parents d5f1213 + 5fb7029 commit f45b755
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 1 deletion.
16 changes: 16 additions & 0 deletions CHANGES.rst
@@ -1,6 +1,22 @@
v4.4.0
=======

* #300: Restore compatibility in the result from
``Distribution.entry_points`` (``EntryPoints``) to honor
expectations in older implementations and issuing
deprecation warnings for these cases:

- ``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()``).

- ``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]``).

* Remove SelectableGroups deprecation exception for flake8.

v4.3.1
Expand Down
21 changes: 21 additions & 0 deletions conftest.py
@@ -1,4 +1,25 @@
import sys


collect_ignore = [
# this module fails mypy tests because 'setup.py' matches './setup.py'
'prepare/example/setup.py',
]


def pytest_configure():
remove_importlib_metadata()


def remove_importlib_metadata():
"""
Because pytest imports importlib_metadata, the coverage
reports are broken (#322). So work around the issue by
undoing the changes made by pytest's import of
importlib_metadata (if any).
"""
if sys.meta_path[-1].__class__.__name__ == 'MetadataPathFinder':
del sys.meta_path[-1]
for mod in list(sys.modules):
if mod.startswith('importlib_metadata'):
del sys.modules[mod]
103 changes: 102 additions & 1 deletion importlib_metadata/__init__.py
Expand Up @@ -209,7 +209,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 @@ -220,6 +313,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 tests/test_api.py
Expand Up @@ -133,6 +133,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
Expand Down

0 comments on commit f45b755

Please sign in to comment.