Skip to content

Commit fa620f1

Browse files
authored
Merge pull request #339 from python/feature/337-EntryPoint-object
Exploring EntryPoint as a simple object
2 parents 0bc774f + 6bdddca commit fa620f1

File tree

4 files changed

+49
-43
lines changed

4 files changed

+49
-43
lines changed

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
v4.8.0
2+
======
3+
4+
* #337: Rewrote ``EntryPoint`` as a simple class, still
5+
immutable and still with the attributes, but without any
6+
expectation for ``namedtuple`` functionality such as
7+
``_asdict``.
8+
19
v4.7.1
210
======
311

importlib_metadata/__init__.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from ._collections import FreezableDefaultDict, Pair
1919
from ._compat import (
2020
NullFinder,
21-
PyPy_repr,
2221
install,
2322
pypy_partial,
2423
)
@@ -126,9 +125,7 @@ def valid(line):
126125
return line and not line.startswith('#')
127126

128127

129-
class EntryPoint(
130-
PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group')
131-
):
128+
class EntryPoint:
132129
"""An entry point as defined by Python packaging conventions.
133130
134131
See `the packaging docs on entry points
@@ -159,6 +156,9 @@ class EntryPoint(
159156

160157
dist: Optional['Distribution'] = None
161158

159+
def __init__(self, name, value, group):
160+
vars(self).update(name=name, value=value, group=group)
161+
162162
def load(self):
163163
"""Load the entry point from its definition. If only a module
164164
is indicated by the value, return that module. Otherwise,
@@ -185,7 +185,7 @@ def extras(self):
185185
return list(re.finditer(r'\w+', match.group('extras') or ''))
186186

187187
def _for(self, dist):
188-
self.dist = dist
188+
vars(self).update(dist=dist)
189189
return self
190190

191191
def __iter__(self):
@@ -199,16 +199,31 @@ def __iter__(self):
199199
warnings.warn(msg, DeprecationWarning)
200200
return iter((self.name, self))
201201

202-
def __reduce__(self):
203-
return (
204-
self.__class__,
205-
(self.name, self.value, self.group),
206-
)
207-
208202
def matches(self, **params):
209203
attrs = (getattr(self, param) for param in params)
210204
return all(map(operator.eq, params.values(), attrs))
211205

206+
def _key(self):
207+
return self.name, self.value, self.group
208+
209+
def __lt__(self, other):
210+
return self._key() < other._key()
211+
212+
def __eq__(self, other):
213+
return self._key() == other._key()
214+
215+
def __setattr__(self, name, value):
216+
raise AttributeError("EntryPoint objects are immutable.")
217+
218+
def __repr__(self):
219+
return (
220+
f'EntryPoint(name={self.name!r}, value={self.value!r}, '
221+
f'group={self.group!r})'
222+
)
223+
224+
def __hash__(self):
225+
return hash(self._key())
226+
212227

213228
class DeprecatedList(list):
214229
"""
@@ -356,15 +371,11 @@ def groups(self):
356371
def _from_text_for(cls, text, dist):
357372
return cls(ep._for(dist) for ep in cls._from_text(text))
358373

359-
@classmethod
360-
def _from_text(cls, text):
361-
return itertools.starmap(EntryPoint, cls._parse_groups(text or ''))
362-
363374
@staticmethod
364-
def _parse_groups(text):
375+
def _from_text(text):
365376
return (
366-
(item.value.name, item.value.value, item.name)
367-
for item in Sectioned.section_pairs(text)
377+
EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
378+
for item in Sectioned.section_pairs(text or '')
368379
)
369380

370381

importlib_metadata/_compat.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import platform
33

44

5-
__all__ = ['install', 'NullFinder', 'PyPy_repr', 'Protocol']
5+
__all__ = ['install', 'NullFinder', 'Protocol']
66

77

88
try:
@@ -66,27 +66,6 @@ def find_spec(*args, **kwargs):
6666
find_module = find_spec
6767

6868

69-
class PyPy_repr:
70-
"""
71-
Override repr for EntryPoint objects on PyPy to avoid __iter__ access.
72-
Ref #97, #102.
73-
"""
74-
75-
affected = hasattr(sys, 'pypy_version_info')
76-
77-
def __compat_repr__(self): # pragma: nocover
78-
def make_param(name):
79-
value = getattr(self, name)
80-
return f'{name}={value!r}'
81-
82-
params = ', '.join(map(make_param, self._fields))
83-
return f'EntryPoint({params})'
84-
85-
if affected: # pragma: nocover
86-
__repr__ = __compat_repr__
87-
del affected
88-
89-
9069
def pypy_partial(val):
9170
"""
9271
Adjust for variable stacklevel on partial under PyPy.

tests/test_main.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,20 @@ def test_discovery(self):
224224
class TestEntryPoints(unittest.TestCase):
225225
def __init__(self, *args):
226226
super().__init__(*args)
227-
self.ep = importlib_metadata.EntryPoint('name', 'value', 'group')
227+
self.ep = importlib_metadata.EntryPoint(
228+
name='name', value='value', group='group'
229+
)
228230

229231
def test_entry_point_pickleable(self):
230232
revived = pickle.loads(pickle.dumps(self.ep))
231233
assert revived == self.ep
232234

235+
def test_positional_args(self):
236+
"""
237+
Capture legacy (namedtuple) construction, discouraged.
238+
"""
239+
EntryPoint('name', 'value', 'group')
240+
233241
def test_immutable(self):
234242
"""EntryPoints should be immutable"""
235243
with self.assertRaises(AttributeError):
@@ -264,8 +272,8 @@ def test_sortable(self):
264272
"""
265273
sorted(
266274
[
267-
EntryPoint('b', 'val', 'group'),
268-
EntryPoint('a', 'val', 'group'),
275+
EntryPoint(name='b', value='val', group='group'),
276+
EntryPoint(name='a', value='val', group='group'),
269277
]
270278
)
271279

0 commit comments

Comments
 (0)