Skip to content

Commit

Permalink
Merge pull request #339 from python/feature/337-EntryPoint-object
Browse files Browse the repository at this point in the history
Exploring EntryPoint as a simple object
  • Loading branch information
jaraco authored Aug 29, 2021
2 parents 0bc774f + 6bdddca commit fa620f1
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 43 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
v4.8.0
======

* #337: Rewrote ``EntryPoint`` as a simple class, still
immutable and still with the attributes, but without any
expectation for ``namedtuple`` functionality such as
``_asdict``.

v4.7.1
======

Expand Down
47 changes: 29 additions & 18 deletions importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
PyPy_repr,
install,
pypy_partial,
)
Expand Down Expand Up @@ -126,9 +125,7 @@ def valid(line):
return line and not line.startswith('#')


class EntryPoint(
PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group')
):
class EntryPoint:
"""An entry point as defined by Python packaging conventions.
See `the packaging docs on entry points
Expand Down Expand Up @@ -159,6 +156,9 @@ class EntryPoint(

dist: Optional['Distribution'] = None

def __init__(self, name, value, group):
vars(self).update(name=name, value=value, group=group)

def load(self):
"""Load the entry point from its definition. If only a module
is indicated by the value, return that module. Otherwise,
Expand All @@ -185,7 +185,7 @@ def extras(self):
return list(re.finditer(r'\w+', match.group('extras') or ''))

def _for(self, dist):
self.dist = dist
vars(self).update(dist=dist)
return self

def __iter__(self):
Expand All @@ -199,16 +199,31 @@ def __iter__(self):
warnings.warn(msg, DeprecationWarning)
return iter((self.name, self))

def __reduce__(self):
return (
self.__class__,
(self.name, self.value, self.group),
)

def matches(self, **params):
attrs = (getattr(self, param) for param in params)
return all(map(operator.eq, params.values(), attrs))

def _key(self):
return self.name, self.value, self.group

def __lt__(self, other):
return self._key() < other._key()

def __eq__(self, other):
return self._key() == other._key()

def __setattr__(self, name, value):
raise AttributeError("EntryPoint objects are immutable.")

def __repr__(self):
return (
f'EntryPoint(name={self.name!r}, value={self.value!r}, '
f'group={self.group!r})'
)

def __hash__(self):
return hash(self._key())


class DeprecatedList(list):
"""
Expand Down Expand Up @@ -356,15 +371,11 @@ def groups(self):
def _from_text_for(cls, text, dist):
return cls(ep._for(dist) for ep in cls._from_text(text))

@classmethod
def _from_text(cls, text):
return itertools.starmap(EntryPoint, cls._parse_groups(text or ''))

@staticmethod
def _parse_groups(text):
def _from_text(text):
return (
(item.value.name, item.value.value, item.name)
for item in Sectioned.section_pairs(text)
EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
for item in Sectioned.section_pairs(text or '')
)


Expand Down
23 changes: 1 addition & 22 deletions importlib_metadata/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import platform


__all__ = ['install', 'NullFinder', 'PyPy_repr', 'Protocol']
__all__ = ['install', 'NullFinder', 'Protocol']


try:
Expand Down Expand Up @@ -66,27 +66,6 @@ def find_spec(*args, **kwargs):
find_module = find_spec


class PyPy_repr:
"""
Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
Ref #97, #102.
"""

affected = hasattr(sys, 'pypy_version_info')

def __compat_repr__(self): # pragma: nocover
def make_param(name):
value = getattr(self, name)
return f'{name}={value!r}'

params = ', '.join(map(make_param, self._fields))
return f'EntryPoint({params})'

if affected: # pragma: nocover
__repr__ = __compat_repr__
del affected


def pypy_partial(val):
"""
Adjust for variable stacklevel on partial under PyPy.
Expand Down
14 changes: 11 additions & 3 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,20 @@ def test_discovery(self):
class TestEntryPoints(unittest.TestCase):
def __init__(self, *args):
super().__init__(*args)
self.ep = importlib_metadata.EntryPoint('name', 'value', 'group')
self.ep = importlib_metadata.EntryPoint(
name='name', value='value', group='group'
)

def test_entry_point_pickleable(self):
revived = pickle.loads(pickle.dumps(self.ep))
assert revived == self.ep

def test_positional_args(self):
"""
Capture legacy (namedtuple) construction, discouraged.
"""
EntryPoint('name', 'value', 'group')

def test_immutable(self):
"""EntryPoints should be immutable"""
with self.assertRaises(AttributeError):
Expand Down Expand Up @@ -264,8 +272,8 @@ def test_sortable(self):
"""
sorted(
[
EntryPoint('b', 'val', 'group'),
EntryPoint('a', 'val', 'group'),
EntryPoint(name='b', value='val', group='group'),
EntryPoint(name='a', value='val', group='group'),
]
)

Expand Down

0 comments on commit fa620f1

Please sign in to comment.