@@ -56,11 +56,15 @@ class TypeParser(ty.Generic[T]):
56
56
the tree of more complex nested container types. Overrides 'coercible' to enable
57
57
you to carve out exceptions, such as TypeParser(list, coercible=[(ty.Iterable, list)],
58
58
not_coercible=[(str, list)])
59
+ label : str
60
+ the label to be used to identify the type parser in error messages. Especially
61
+ useful when TypeParser is used as a converter in attrs.fields
59
62
"""
60
63
61
64
tp : ty .Type [T ]
62
65
coercible : ty .List [ty .Tuple [TypeOrAny , TypeOrAny ]]
63
66
not_coercible : ty .List [ty .Tuple [TypeOrAny , TypeOrAny ]]
67
+ label : str
64
68
65
69
COERCIBLE_DEFAULT : ty .Tuple [ty .Tuple [type , type ], ...] = (
66
70
(
@@ -103,6 +107,7 @@ def __init__(
103
107
not_coercible : ty .Optional [
104
108
ty .Iterable [ty .Tuple [TypeOrAny , TypeOrAny ]]
105
109
] = NOT_COERCIBLE_DEFAULT ,
110
+ label : str = "" ,
106
111
):
107
112
def expand_pattern (t ):
108
113
"""Recursively expand the type arguments of the target type in nested tuples"""
@@ -118,10 +123,12 @@ def expand_pattern(t):
118
123
return origin
119
124
if origin not in (ty .Union , type ) and not issubclass (origin , ty .Iterable ):
120
125
raise TypeError (
121
- f"TypeParser doesn't know how to handle args ({ args } ) for { origin } types"
126
+ f"TypeParser doesn't know how to handle args ({ args } ) for { origin } "
127
+ f"types{ self .label_str } "
122
128
)
123
129
return (origin , [expand_pattern (a ) for a in args ])
124
130
131
+ self .label = label
125
132
self .tp = tp
126
133
self .coercible = (
127
134
list (coercible ) if coercible is not None else [(ty .Any , ty .Any )]
@@ -194,7 +201,7 @@ def expand_and_coerce(obj, pattern: ty.Union[type, tuple]):
194
201
else ""
195
202
)
196
203
raise TypeError (
197
- f"Could not coerce to { type_ } as { obj !r} is not iterable{ msg } "
204
+ f"Could not coerce to { type_ } as { obj !r} is not iterable{ msg } { self . label_str } "
198
205
) from e
199
206
if issubclass (origin , tuple ):
200
207
return coerce_tuple (type_ , obj_args , pattern_args )
@@ -221,7 +228,8 @@ def coerce_union(obj, pattern_args):
221
228
except TypeError as e :
222
229
reasons .append (e )
223
230
raise TypeError (
224
- f"Could not coerce object, { obj !r} , to any of the union types { pattern_args } :\n \n "
231
+ f"Could not coerce object, { obj !r} , to any of the union types "
232
+ f"{ pattern_args } { self .label_str } :\n \n "
225
233
+ "\n \n " .join (f"{ a } -> { e } " for a , e in zip (pattern_args , reasons ))
226
234
)
227
235
@@ -240,7 +248,7 @@ def coerce_mapping(
240
248
else ""
241
249
)
242
250
raise TypeError (
243
- f"Could not coerce to { type_ } as { obj } is not a mapping type{ msg } "
251
+ f"Could not coerce to { type_ } as { obj } is not a mapping type{ msg } { self . label_str } "
244
252
) from e
245
253
return coerce_obj (
246
254
{
@@ -263,7 +271,7 @@ def coerce_tuple(
263
271
elif len (pattern_args ) != len (obj_args ):
264
272
raise TypeError (
265
273
f"Incorrect number of items in tuple, expected "
266
- f"{ len (pattern_args )} , got { len (obj_args )} "
274
+ f"{ len (pattern_args )} , got { len (obj_args )} { self . label_str } "
267
275
)
268
276
return coerce_obj (
269
277
[expand_and_coerce (o , p ) for o , p in zip (obj_args , pattern_args )], type_
@@ -281,7 +289,7 @@ def coerce_sequence(
281
289
def coerce_type (type_ : ty .Type [ty .Any ], pattern_args : ty .List [ty .Type [ty .Any ]]):
282
290
if not any (issubclass (type_ , t ) for t in pattern_args ):
283
291
raise TypeError (
284
- f"{ type_ } is not one of the specified types { pattern_args } "
292
+ f"{ type_ } is not one of the specified types { pattern_args } { self . label_str } "
285
293
)
286
294
return type_
287
295
@@ -297,7 +305,9 @@ def coerce_obj(obj, type_):
297
305
if obj is not object_
298
306
else ""
299
307
)
300
- raise TypeError (f"Cannot coerce { obj !r} into { type_ } { msg } " ) from e
308
+ raise TypeError (
309
+ f"Cannot coerce { obj !r} into { type_ } { msg } { self .label_str } "
310
+ ) from e
301
311
302
312
return expand_and_coerce (object_ , self .pattern )
303
313
@@ -323,7 +333,7 @@ def check_type(self, type_: ty.Type[ty.Any]):
323
333
raise TypeError ("Splits without any type arguments are invalid" )
324
334
if len (args ) > 1 :
325
335
raise TypeError (
326
- f"Splits with more than one type argument ({ args } ) are invalid"
336
+ f"Splits with more than one type argument ({ args } ) are invalid{ self . label_str } "
327
337
)
328
338
return self .check_type (args [0 ])
329
339
@@ -343,7 +353,7 @@ def expand_and_check(tp, pattern: ty.Union[type, tuple]):
343
353
)
344
354
raise TypeError (
345
355
f"{ tp } doesn't match pattern { pattern } , when matching { type_ } to "
346
- f"{ self .pattern } "
356
+ f"{ self .pattern } { self . label_str } "
347
357
)
348
358
tp_args = get_args (tp )
349
359
self .check_coercible (tp_origin , pattern_origin )
@@ -378,7 +388,7 @@ def check_union(tp, pattern_args):
378
388
if reasons :
379
389
raise TypeError (
380
390
f"Cannot coerce { tp } to "
381
- f"ty.Union[{ ', ' .join (str (a ) for a in pattern_args )} ], "
391
+ f"ty.Union[{ ', ' .join (str (a ) for a in pattern_args )} ]{ self . label_str } , "
382
392
f"because { tp_arg } cannot be coerced to any of its args:\n \n "
383
393
+ "\n \n " .join (
384
394
f"{ a } -> { e } " for a , e in zip (pattern_args , reasons )
@@ -414,7 +424,7 @@ def check_tuple(tp_args, pattern_args):
414
424
if len (tp_args ) != len (pattern_args ):
415
425
raise TypeError (
416
426
f"Wrong number of type arguments in tuple { tp_args } compared to pattern "
417
- f"{ pattern_args } in attempting to match { type_ } to { self .pattern } "
427
+ f"{ pattern_args } in attempting to match { type_ } to { self .pattern } { self . label_str } "
418
428
)
419
429
for t , p in zip (tp_args , pattern_args ):
420
430
expand_and_check (t , p )
@@ -426,7 +436,8 @@ def check_sequence(tp_args, pattern_args):
426
436
if not tp_args :
427
437
raise TypeError (
428
438
"Generic ellipsis type arguments not specific enough to match "
429
- f"{ pattern_args } in attempting to match { type_ } to { self .pattern } "
439
+ f"{ pattern_args } in attempting to match { type_ } to "
440
+ f"{ self .pattern } { self .label_str } "
430
441
)
431
442
for arg in tp_args :
432
443
expand_and_check (arg , pattern_args [0 ])
@@ -476,16 +487,16 @@ def type_name(t):
476
487
477
488
if not matches_criteria (self .coercible ):
478
489
raise TypeError (
479
- f"Cannot coerce { repr (source )} into { target } as the coercion doesn't match "
480
- f" any of the explicit inclusion criteria: "
490
+ f"Cannot coerce { repr (source )} into { target } { self . label_str } as the "
491
+ "coercion doesn't match any of the explicit inclusion criteria: "
481
492
+ ", " .join (
482
493
f"{ type_name (s )} -> { type_name (t )} " for s , t in self .coercible
483
494
)
484
495
)
485
496
matches_not_coercible = matches_criteria (self .not_coercible )
486
497
if matches_not_coercible :
487
498
raise TypeError (
488
- f"Cannot coerce { repr (source )} into { target } as it is explicitly "
499
+ f"Cannot coerce { repr (source )} into { target } { self . label_str } as it is explicitly "
489
500
"excluded by the following coercion criteria: "
490
501
+ ", " .join (
491
502
f"{ type_name (s )} -> { type_name (t )} "
@@ -799,5 +810,9 @@ def strip_splits(cls, type_: ty.Type[ty.Any]) -> ty.Tuple[ty.Type, int]:
799
810
depth += 1
800
811
return type_ , depth
801
812
813
+ @property
814
+ def label_str (self ):
815
+ return f" in { self .label } " if self .label else ""
816
+
802
817
get_origin = staticmethod (get_origin )
803
818
get_args = staticmethod (get_args )
0 commit comments