9
9
from attr import has as attrs_has
10
10
from attr import resolve_types
11
11
12
+ from cattrs .errors import (
13
+ IterableValidationError ,
14
+ StructureHandlerNotFoundError ,
15
+ )
16
+
12
17
from ._compat import (
13
18
FrozenSetSubscriptable ,
14
19
Mapping ,
38
43
)
39
44
from .disambiguators import create_uniq_field_dis_func
40
45
from .dispatch import MultiStrategyDispatch
41
- from .errors import StructureHandlerNotFoundError
42
46
from .gen import (
43
47
AttributeOverride ,
44
48
make_dict_structure_fn ,
@@ -86,7 +90,7 @@ def is_optional(typ):
86
90
)
87
91
88
92
89
- class Converter ( object ) :
93
+ class Converter :
90
94
"""Converts between structured and unstructured data."""
91
95
92
96
__slots__ = (
@@ -98,17 +102,21 @@ class Converter(object):
98
102
"_union_struct_registry" ,
99
103
"_structure_func" ,
100
104
"_prefer_attrib_converters" ,
105
+ "extended_validation" ,
101
106
)
102
107
103
108
def __init__ (
104
109
self ,
105
110
dict_factory : Callable [[], Any ] = dict ,
106
111
unstruct_strat : UnstructureStrategy = UnstructureStrategy .AS_DICT ,
107
112
prefer_attrib_converters : bool = False ,
113
+ extended_validation : bool = True ,
108
114
) -> None :
109
115
unstruct_strat = UnstructureStrategy (unstruct_strat )
110
116
self ._prefer_attrib_converters = prefer_attrib_converters
111
117
118
+ self .extended_validation = extended_validation
119
+
112
120
# Create a per-instance cache.
113
121
if unstruct_strat is UnstructureStrategy .AS_DICT :
114
122
self ._unstructure_attrs = self .unstructure_attrs_asdict
@@ -471,14 +479,32 @@ def structure_attrs_fromdict(
471
479
472
480
def _structure_list (self , obj , cl ):
473
481
"""Convert an iterable to a potentially generic list."""
474
- if is_bare (cl ) or cl .__args__ [0 ] is Any :
475
- return [e for e in obj ]
482
+ if self .extended_validation :
483
+ errors = {}
484
+ if is_bare (cl ) or cl .__args__ [0 ] is Any :
485
+ res = [e for e in obj ]
486
+ else :
487
+ elem_type = cl .__args__ [0 ]
488
+ handler = self ._structure_func .dispatch (elem_type )
489
+ res = []
490
+ ix = 0 # Avoid `enumerate` for performance.
491
+ for e in obj :
492
+ try :
493
+ res .append (handler (e , elem_type ))
494
+ except Exception as e :
495
+ errors [ix ] = e
496
+ finally :
497
+ ix += 1
498
+ if errors :
499
+ raise IterableValidationError (errors )
476
500
else :
477
- elem_type = cl .__args__ [0 ]
478
- return [
479
- self ._structure_func .dispatch (elem_type )(e , elem_type )
480
- for e in obj
481
- ]
501
+ if is_bare (cl ) or cl .__args__ [0 ] is Any :
502
+ res = [e for e in obj ]
503
+ else :
504
+ elem_type = cl .__args__ [0 ]
505
+ handler = self ._structure_func .dispatch (elem_type )
506
+ res = [handler (e , elem_type ) for e in obj ]
507
+ return res
482
508
483
509
def _structure_set (self , obj , cl ):
484
510
"""Convert an iterable into a potentially generic set."""
@@ -597,11 +623,13 @@ def __init__(
597
623
type_overrides : Mapping [Type , AttributeOverride ] = {},
598
624
unstruct_collection_overrides : Mapping [Type , Callable ] = {},
599
625
prefer_attrib_converters : bool = False ,
626
+ extended_validation : bool = True ,
600
627
):
601
628
super ().__init__ (
602
629
dict_factory = dict_factory ,
603
630
unstruct_strat = unstruct_strat ,
604
631
prefer_attrib_converters = prefer_attrib_converters ,
632
+ extended_validation = extended_validation ,
605
633
)
606
634
self .omit_if_default = omit_if_default
607
635
self .forbid_extra_keys = forbid_extra_keys
@@ -742,6 +770,7 @@ def gen_structure_attrs_fromdict(self, cl: Type[T]) -> T:
742
770
self ,
743
771
_cattrs_forbid_extra_keys = self .forbid_extra_keys ,
744
772
_cattrs_prefer_attrib_converters = self ._prefer_attrib_converters ,
773
+ _cattrs_extended_validation = self .extended_validation ,
745
774
** attrib_overrides ,
746
775
)
747
776
# only direct dispatch so that subclasses get separately generated
0 commit comments