5
5
import sys
6
6
import zipp
7
7
import email
8
+ import inspect
8
9
import pathlib
9
10
import operator
11
+ import warnings
10
12
import functools
11
13
import itertools
12
14
import posixpath
@@ -130,22 +132,19 @@ def _from_text(cls, text):
130
132
config .read_string (text )
131
133
return cls ._from_config (config )
132
134
133
- @classmethod
134
- def _from_text_for (cls , text , dist ):
135
- return (ep ._for (dist ) for ep in cls ._from_text (text ))
136
-
137
135
def _for (self , dist ):
138
136
self .dist = dist
139
137
return self
140
138
141
139
def __iter__ (self ):
142
140
"""
143
141
Supply iter so one may construct dicts of EntryPoints by name.
144
-
145
- >>> eps = [EntryPoint('a', 'b', 'c'), EntryPoint('d', 'e', 'f')]
146
- >>> dict(eps)['a']
147
- EntryPoint(name='a', value='b', group='c')
148
142
"""
143
+ msg = (
144
+ "Construction of dict of EntryPoints is deprecated in "
145
+ "favor of EntryPoints."
146
+ )
147
+ warnings .warn (msg , DeprecationWarning )
149
148
return iter ((self .name , self ))
150
149
151
150
def __reduce__ (self ):
@@ -154,6 +153,118 @@ def __reduce__(self):
154
153
(self .name , self .value , self .group ),
155
154
)
156
155
156
+ def matches (self , ** params ):
157
+ attrs = (getattr (self , param ) for param in params )
158
+ return all (map (operator .eq , params .values (), attrs ))
159
+
160
+
161
+ class EntryPoints (tuple ):
162
+ """
163
+ An immutable collection of selectable EntryPoint objects.
164
+ """
165
+
166
+ __slots__ = ()
167
+
168
+ def __getitem__ (self , name ): # -> EntryPoint:
169
+ try :
170
+ return next (iter (self .select (name = name )))
171
+ except StopIteration :
172
+ raise KeyError (name )
173
+
174
+ def select (self , ** params ):
175
+ return EntryPoints (ep for ep in self if ep .matches (** params ))
176
+
177
+ @property
178
+ def names (self ):
179
+ return set (ep .name for ep in self )
180
+
181
+ @property
182
+ def groups (self ):
183
+ """
184
+ For coverage while SelectableGroups is present.
185
+ >>> EntryPoints().groups
186
+ set()
187
+ """
188
+ return set (ep .group for ep in self )
189
+
190
+ @classmethod
191
+ def _from_text_for (cls , text , dist ):
192
+ return cls (ep ._for (dist ) for ep in EntryPoint ._from_text (text ))
193
+
194
+
195
+ class SelectableGroups (dict ):
196
+ """
197
+ A backward- and forward-compatible result from
198
+ entry_points that fully implements the dict interface.
199
+ """
200
+
201
+ @classmethod
202
+ def load (cls , eps ):
203
+ by_group = operator .attrgetter ('group' )
204
+ ordered = sorted (eps , key = by_group )
205
+ grouped = itertools .groupby (ordered , by_group )
206
+ return cls ((group , EntryPoints (eps )) for group , eps in grouped )
207
+
208
+ @property
209
+ def _all (self ):
210
+ return EntryPoints (itertools .chain .from_iterable (self .values ()))
211
+
212
+ @property
213
+ def groups (self ):
214
+ return self ._all .groups
215
+
216
+ @property
217
+ def names (self ):
218
+ """
219
+ for coverage:
220
+ >>> SelectableGroups().names
221
+ set()
222
+ """
223
+ return self ._all .names
224
+
225
+ def select (self , ** params ):
226
+ if not params :
227
+ return self
228
+ return self ._all .select (** params )
229
+
230
+
231
+ class LegacyGroupedEntryPoints (EntryPoints ): # pragma: nocover
232
+ """
233
+ Compatibility wrapper around EntryPoints to provide
234
+ much of the 'dict' interface previously returned by
235
+ entry_points.
236
+ """
237
+
238
+ def __getitem__ (self , name ) -> Union [EntryPoint , 'EntryPoints' ]:
239
+ """
240
+ When accessed by name that matches a group, return the group.
241
+ """
242
+ group = self .select (group = name )
243
+ if group :
244
+ msg = "GroupedEntryPoints.__getitem__ is deprecated for groups. Use select."
245
+ warnings .warn (msg , DeprecationWarning , stacklevel = 2 )
246
+ return group
247
+
248
+ return super ().__getitem__ (name )
249
+
250
+ def get (self , group , default = None ):
251
+ """
252
+ For backward compatibility, supply .get.
253
+ """
254
+ is_flake8 = any ('flake8' in str (frame ) for frame in inspect .stack ())
255
+ msg = "GroupedEntryPoints.get is deprecated. Use select."
256
+ is_flake8 or warnings .warn (msg , DeprecationWarning , stacklevel = 2 )
257
+ return self .select (group = group ) or default
258
+
259
+ def select (self , ** params ):
260
+ """
261
+ Prevent transform to EntryPoints during call to entry_points if
262
+ no selection parameters were passed.
263
+ """
264
+ if not params :
265
+ return self
266
+ return super ().select (** params )
267
+
157
268
158
269
class PackagePath (pathlib .PurePosixPath ):
159
270
"""A reference to a path in a package"""
@@ -310,7 +421,7 @@ def version(self):
310
421
311
422
@property
312
423
def entry_points (self ):
313
- return list ( EntryPoint ._from_text_for (self .read_text ('entry_points.txt' ), self ) )
424
+ return EntryPoints ._from_text_for (self .read_text ('entry_points.txt' ), self )
314
425
315
426
@property
316
427
def files (self ):
@@ -643,19 +754,29 @@ def version(distribution_name):
643
754
return distribution (distribution_name ).version
644
755
645
756
646
- def entry_points () :
757
+ def entry_points (** params ) -> Union [ EntryPoints , SelectableGroups ] :
647
758
"""Return EntryPoint objects for all installed packages.
648
759
649
- :return: EntryPoint objects for all installed packages.
760
+ Pass selection parameters (group or name) to filter the
761
+ result to entry points matching those properties (see
762
+ EntryPoints.select()).
763
+
764
+ For compatibility, returns ``SelectableGroups`` object unless
765
+ selection parameters are supplied. In the future, this function
766
+ will return ``LegacyGroupedEntryPoints`` instead of
767
+ ``SelectableGroups`` and eventually will only return
768
+ ``EntryPoints``.
769
+
770
+ For maximum future compatibility, pass selection parameters
771
+ or invoke ``.select`` with parameters on the result.
772
+
773
+ :return: EntryPoints or SelectableGroups for all installed packages.
650
774
"""
651
775
unique = functools .partial (unique_everseen , key = operator .attrgetter ('name' ))
652
776
eps = itertools .chain .from_iterable (
653
777
dist .entry_points for dist in unique (distributions ())
654
778
)
655
- by_group = operator .attrgetter ('group' )
656
- ordered = sorted (eps , key = by_group )
657
- grouped = itertools .groupby (ordered , by_group )
658
- return {group : tuple (eps ) for group , eps in grouped }
779
+ return SelectableGroups .load (eps ).select (** params )
659
780
660
781
661
782
def files (distribution_name ):
0 commit comments