66import re
77from dataclasses import dataclass , field
88from pathlib import Path
9- from typing import Optional , Iterator , Iterable , List , TypeVar
9+ from typing import Optional , Iterator , Iterable , List , TypeVar , Generic
1010
1111
12- @dataclass
13- class MetadataError :
14- file : Optional [Path ] = None
15- id : Optional [str ] = None
16-
17- def prefix (self ):
18- prefix = f"In { self .file or 'several' } at { self .id } ,"
19- return prefix
20-
21- def message (self ) -> str :
22- return ""
23-
24- def __str__ (self ):
25- return f"{ self .prefix ()} { self .message ()} "
12+ ErrorT = TypeVar ("ErrorT" )
2613
2714
28- @dataclass
29- class MetadataParseError (MetadataError ):
30- id : Optional [str ] = None
31- language : Optional [str ] = None
32- sdk_version : Optional [int ] = None
33-
34- def prefix (self ):
35- prefix = super ().prefix () + f" example { self .id } "
36- if self .language :
37- prefix += f" { self .language } "
38- if self .sdk_version :
39- prefix += f":{ self .sdk_version } "
40- return prefix
41-
42- def __str__ (self ):
43- return f"{ self .prefix ()} { self .message ()} "
44-
45-
46- K = TypeVar ("K" )
47-
48-
49- class InvalidItemException (Exception ):
50- def __init__ (self , item : MetadataParseError ):
15+ class InvalidItemException (Exception , Generic [ErrorT ]):
16+ def __init__ (self , item : ErrorT ):
5117 super ().__init__ (self , f"Cannot append { item !r} to ExampleErrors" )
5218
5319
54- class DuplicateItemException (Exception ):
55- def __init__ (self , item : MetadataError ):
56- super ().__init__ (self , f"Already have item { item !r} in ExampleErrors" )
57-
58-
59- class MetadataErrors :
20+ class ErrorsList (Generic [ErrorT ]):
6021 """MyPy isn't catching List[Foo].append(List[Foo])"""
6122
6223 def __init__ (self , no_duplicates : bool = False ):
6324 self .no_duplicates = no_duplicates
64- self ._errors : List [MetadataError ] = []
65-
66- def append (self , item : MetadataError ):
67- if not isinstance (item , MetadataError ):
25+ self ._errors : List [ErrorT ] = []
26+
27+ def append (self , item : ErrorT ):
28+ # Look up the generic type. This is reliant on the internal implementation
29+ # of __orig_bases__, but it will definitely fail tests if a python minor
30+ # version breaks it.
31+ generic = self .__orig_bases__ [0 ] # type: ignore
32+ T = generic .__args__ [0 ]
33+ if not isinstance (item , T ):
6834 raise InvalidItemException (item )
35+
6936 """
7037 It is dangerous to go alone: 🗡️
7138 If you're seeing duplicated Errors, and aren't sure why, uncommenting these lines may help you debug it.
@@ -74,25 +41,25 @@ def append(self, item: MetadataError):
7441 # raise DuplicateItemException(item)
7542 self ._errors .append (item )
7643
77- def extend (self , errors : Iterable [MetadataError ]):
44+ def extend (self , errors : Iterable [ErrorT ]):
7845 self ._errors .extend (errors )
7946
80- def maybe_extend (self , maybe_errors : K | MetadataErrors ) -> K | None :
81- if isinstance (maybe_errors , MetadataErrors ):
47+ def maybe_extend (self , maybe_errors : K | ErrorsList [ ErrorT ] ) -> K | None :
48+ if isinstance (maybe_errors , ErrorsList ):
8249 self .extend (maybe_errors ._errors )
8350 return None
8451 return maybe_errors
8552
86- def __getitem__ (self , key : int ) -> MetadataError :
53+ def __getitem__ (self , key : int ) -> ErrorT :
8754 return self ._errors [key ]
8855
89- def __setitem__ (self , key : int , value : MetadataError ):
56+ def __setitem__ (self , key : int , value : ErrorT ):
9057 self ._errors [key ] = value
9158
9259 def __len__ (self ) -> int :
9360 return len (self ._errors )
9461
95- def __iter__ (self ) -> Iterator [MetadataError ]:
62+ def __iter__ (self ) -> Iterator [ErrorT ]:
9663 return self ._errors .__iter__ ()
9764
9865 def __repr__ (self ) -> str :
@@ -103,7 +70,53 @@ def __str__(self) -> str:
10370 return f"ExampleErrors with { len (self )} errors:\n { errs } "
10471
10572 def __eq__ (self , __value : object ) -> bool :
106- return isinstance (__value , MetadataErrors ) and self ._errors == __value ._errors
73+ return isinstance (__value , ErrorsList ) and self ._errors == __value ._errors
74+
75+
76+ @dataclass
77+ class MetadataError :
78+ file : Optional [Path ] = None
79+ id : Optional [str ] = None
80+
81+ def prefix (self ):
82+ prefix = f"In { self .file or 'several' } at { self .id } ,"
83+ return prefix
84+
85+ def message (self ) -> str :
86+ return ""
87+
88+ def __str__ (self ):
89+ return f"{ self .prefix ()} { self .message ()} "
90+
91+
92+ class MetadataErrors (ErrorsList [MetadataError ]):
93+ pass
94+
95+
96+ @dataclass
97+ class MetadataParseError (MetadataError ):
98+ id : Optional [str ] = None
99+ language : Optional [str ] = None
100+ sdk_version : Optional [int ] = None
101+
102+ def prefix (self ):
103+ prefix = super ().prefix () + f" example { self .id } "
104+ if self .language :
105+ prefix += f" { self .language } "
106+ if self .sdk_version :
107+ prefix += f":{ self .sdk_version } "
108+ return prefix
109+
110+ def __str__ (self ):
111+ return f"{ self .prefix ()} { self .message ()} "
112+
113+
114+ K = TypeVar ("K" )
115+
116+
117+ class DuplicateItemException (Exception ):
118+ def __init__ (self , item : MetadataError ):
119+ super ().__init__ (self , f"Already have item { item !r} in ExampleErrors" )
107120
108121
109122@dataclass
0 commit comments