1515import collections
1616
1717from . import _adapters , _meta
18- from ._meta import PackageMetadata
1918from ._collections import FreezableDefaultDict , Pair
20- from ._functools import method_cache
21- from ._itertools import unique_everseen
19+ from ._functools import method_cache , pass_none
20+ from ._itertools import always_iterable , unique_everseen
2221from ._meta import PackageMetadata , SimplePath
2322
2423from contextlib import suppress
@@ -121,8 +120,33 @@ def valid(line):
121120 return line and not line .startswith ('#' )
122121
123122
124- class EntryPoint (
125- collections .namedtuple ('EntryPointBase' , 'name value group' )):
123+ class DeprecatedTuple :
124+ """
125+ Provide subscript item access for backward compatibility.
126+
127+ >>> recwarn = getfixture('recwarn')
128+ >>> ep = EntryPoint(name='name', value='value', group='group')
129+ >>> ep[:]
130+ ('name', 'value', 'group')
131+ >>> ep[0]
132+ 'name'
133+ >>> len(recwarn)
134+ 1
135+ """
136+
137+ _warn = functools .partial (
138+ warnings .warn ,
139+ "EntryPoint tuple interface is deprecated. Access members by name." ,
140+ DeprecationWarning ,
141+ stacklevel = 2 ,
142+ )
143+
144+ def __getitem__ (self , item ):
145+ self ._warn ()
146+ return self ._key ()[item ]
147+
148+
149+ class EntryPoint (DeprecatedTuple ):
126150 """An entry point as defined by Python packaging conventions.
127151
128152 See `the packaging docs on entry points
@@ -153,6 +177,9 @@ class EntryPoint(
153177
154178 dist : Optional ['Distribution' ] = None
155179
180+ def __init__ (self , name , value , group ):
181+ vars (self ).update (name = name , value = value , group = group )
182+
156183 def load (self ):
157184 """Load the entry point from its definition. If only a module
158185 is indicated by the value, return that module. Otherwise,
@@ -179,7 +206,7 @@ def extras(self):
179206 return list (re .finditer (r'\w+' , match .group ('extras' ) or '' ))
180207
181208 def _for (self , dist ):
182- self . dist = dist
209+ vars ( self ). update ( dist = dist )
183210 return self
184211
185212 def __iter__ (self ):
@@ -193,16 +220,31 @@ def __iter__(self):
193220 warnings .warn (msg , DeprecationWarning )
194221 return iter ((self .name , self ))
195222
196- def __reduce__ (self ):
197- return (
198- self .__class__ ,
199- (self .name , self .value , self .group ),
200- )
201-
202223 def matches (self , ** params ):
203224 attrs = (getattr (self , param ) for param in params )
204225 return all (map (operator .eq , params .values (), attrs ))
205226
227+ def _key (self ):
228+ return self .name , self .value , self .group
229+
230+ def __lt__ (self , other ):
231+ return self ._key () < other ._key ()
232+
233+ def __eq__ (self , other ):
234+ return self ._key () == other ._key ()
235+
236+ def __setattr__ (self , name , value ):
237+ raise AttributeError ("EntryPoint objects are immutable." )
238+
239+ def __repr__ (self ):
240+ return (
241+ f'EntryPoint(name={ self .name !r} , value={ self .value !r} , '
242+ f'group={ self .group !r} )'
243+ )
244+
245+ def __hash__ (self ):
246+ return hash (self ._key ())
247+
206248
207249class DeprecatedList (list ):
208250 """
@@ -243,52 +285,33 @@ class DeprecatedList(list):
243285 stacklevel = 2 ,
244286 )
245287
246- def __setitem__ (self , * args , ** kwargs ):
247- self ._warn ()
248- return super ().__setitem__ (* args , ** kwargs )
249-
250- def __delitem__ (self , * args , ** kwargs ):
251- self ._warn ()
252- return super ().__delitem__ (* args , ** kwargs )
253-
254- def append (self , * args , ** kwargs ):
255- self ._warn ()
256- return super ().append (* args , ** kwargs )
257-
258- def reverse (self , * args , ** kwargs ):
259- self ._warn ()
260- return super ().reverse (* args , ** kwargs )
261-
262- def extend (self , * args , ** kwargs ):
263- self ._warn ()
264- return super ().extend (* args , ** kwargs )
265-
266- def pop (self , * args , ** kwargs ):
267- self ._warn ()
268- return super ().pop (* args , ** kwargs )
269-
270- def remove (self , * args , ** kwargs ):
271- self ._warn ()
272- return super ().remove (* args , ** kwargs )
273-
274- def __iadd__ (self , * args , ** kwargs ):
275- self ._warn ()
276- return super ().__iadd__ (* args , ** kwargs )
288+ def _wrap_deprecated_method (method_name : str ): # type: ignore
289+ def wrapped (self , * args , ** kwargs ):
290+ self ._warn ()
291+ return getattr (super (), method_name )(* args , ** kwargs )
292+
293+ return wrapped
294+
295+ for method_name in [
296+ '__setitem__' ,
297+ '__delitem__' ,
298+ 'append' ,
299+ 'reverse' ,
300+ 'extend' ,
301+ 'pop' ,
302+ 'remove' ,
303+ '__iadd__' ,
304+ 'insert' ,
305+ 'sort' ,
306+ ]:
307+ locals ()[method_name ] = _wrap_deprecated_method (method_name )
277308
278309 def __add__ (self , other ):
279310 if not isinstance (other , tuple ):
280311 self ._warn ()
281312 other = tuple (other )
282313 return self .__class__ (tuple (self ) + other )
283314
284- def insert (self , * args , ** kwargs ):
285- self ._warn ()
286- return super ().insert (* args , ** kwargs )
287-
288- def sort (self , * args , ** kwargs ):
289- self ._warn ()
290- return super ().sort (* args , ** kwargs )
291-
292315 def __eq__ (self , other ):
293316 if not isinstance (other , tuple ):
294317 self ._warn ()
@@ -333,7 +356,7 @@ def names(self):
333356 """
334357 Return the set of all names of all entry points.
335358 """
336- return set ( ep .name for ep in self )
359+ return { ep .name for ep in self }
337360
338361 @property
339362 def groups (self ):
@@ -344,21 +367,17 @@ def groups(self):
344367 >>> EntryPoints().groups
345368 set()
346369 """
347- return set ( ep .group for ep in self )
370+ return { ep .group for ep in self }
348371
349372 @classmethod
350373 def _from_text_for (cls , text , dist ):
351374 return cls (ep ._for (dist ) for ep in cls ._from_text (text ))
352375
353- @classmethod
354- def _from_text (cls , text ):
355- return itertools .starmap (EntryPoint , cls ._parse_groups (text or '' ))
356-
357376 @staticmethod
358- def _parse_groups (text ):
377+ def _from_text (text ):
359378 return (
360- ( item .value .name , item .value .value , item .name )
361- for item in Sectioned .section_pairs (text )
379+ EntryPoint ( name = item .value .name , value = item .value .value , group = item .name )
380+ for item in Sectioned .section_pairs (text or '' )
362381 )
363382
364383
@@ -611,7 +630,6 @@ def files(self):
611630 missing.
612631 Result may be empty if the metadata exists but is empty.
613632 """
614- file_lines = self ._read_files_distinfo () or self ._read_files_egginfo ()
615633
616634 def make_file (name , hash = None , size_str = None ):
617635 result = PackagePath (name )
@@ -620,7 +638,11 @@ def make_file(name, hash=None, size_str=None):
620638 result .dist = self
621639 return result
622640
623- return file_lines and list (starmap (make_file , csv .reader (file_lines )))
641+ @pass_none
642+ def make_files (lines ):
643+ return list (starmap (make_file , csv .reader (lines )))
644+
645+ return make_files (self ._read_files_distinfo () or self ._read_files_egginfo ())
624646
625647 def _read_files_distinfo (self ):
626648 """
@@ -742,6 +764,9 @@ class FastPath:
742764 """
743765 Micro-optimized class for searching a path for
744766 children.
767+
768+ >>> FastPath('').children()
769+ ['...']
745770 """
746771
747772 @functools .lru_cache () # type: ignore
@@ -1011,6 +1036,18 @@ def packages_distributions() -> Mapping[str, List[str]]:
10111036 """
10121037 pkg_to_dist = collections .defaultdict (list )
10131038 for dist in distributions ():
1014- for pkg in (dist . read_text ( 'top_level.txt' ) or '' ). split ( ):
1039+ for pkg in _top_level_declared (dist ) or _top_level_inferred ( dist ):
10151040 pkg_to_dist [pkg ].append (dist .metadata ['Name' ])
10161041 return dict (pkg_to_dist )
1042+
1043+
1044+ def _top_level_declared (dist ):
1045+ return (dist .read_text ('top_level.txt' ) or '' ).split ()
1046+
1047+
1048+ def _top_level_inferred (dist ):
1049+ return {
1050+ f .parts [0 ] if len (f .parts ) > 1 else f .with_suffix ('' ).name
1051+ for f in always_iterable (dist .files )
1052+ if f .suffix == ".py"
1053+ }
0 commit comments