Skip to content

Commit a6ab503

Browse files
committed
Extended validation first pass
1 parent 3ee4b40 commit a6ab503

File tree

6 files changed

+693
-27
lines changed

6 files changed

+693
-27
lines changed

src/cattr/converters.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
from attr import has as attrs_has
1010
from attr import resolve_types
1111

12+
from cattrs.errors import (
13+
IterableValidationError,
14+
StructureHandlerNotFoundError,
15+
)
16+
1217
from ._compat import (
1318
FrozenSetSubscriptable,
1419
Mapping,
@@ -38,7 +43,6 @@
3843
)
3944
from .disambiguators import create_uniq_field_dis_func
4045
from .dispatch import MultiStrategyDispatch
41-
from .errors import StructureHandlerNotFoundError
4246
from .gen import (
4347
AttributeOverride,
4448
make_dict_structure_fn,
@@ -86,7 +90,7 @@ def is_optional(typ):
8690
)
8791

8892

89-
class Converter(object):
93+
class Converter:
9094
"""Converts between structured and unstructured data."""
9195

9296
__slots__ = (
@@ -98,17 +102,21 @@ class Converter(object):
98102
"_union_struct_registry",
99103
"_structure_func",
100104
"_prefer_attrib_converters",
105+
"extended_validation",
101106
)
102107

103108
def __init__(
104109
self,
105110
dict_factory: Callable[[], Any] = dict,
106111
unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT,
107112
prefer_attrib_converters: bool = False,
113+
extended_validation: bool = True,
108114
) -> None:
109115
unstruct_strat = UnstructureStrategy(unstruct_strat)
110116
self._prefer_attrib_converters = prefer_attrib_converters
111117

118+
self.extended_validation = extended_validation
119+
112120
# Create a per-instance cache.
113121
if unstruct_strat is UnstructureStrategy.AS_DICT:
114122
self._unstructure_attrs = self.unstructure_attrs_asdict
@@ -471,14 +479,32 @@ def structure_attrs_fromdict(
471479

472480
def _structure_list(self, obj, cl):
473481
"""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)
476500
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
482508

483509
def _structure_set(self, obj, cl):
484510
"""Convert an iterable into a potentially generic set."""
@@ -597,11 +623,13 @@ def __init__(
597623
type_overrides: Mapping[Type, AttributeOverride] = {},
598624
unstruct_collection_overrides: Mapping[Type, Callable] = {},
599625
prefer_attrib_converters: bool = False,
626+
extended_validation: bool = True,
600627
):
601628
super().__init__(
602629
dict_factory=dict_factory,
603630
unstruct_strat=unstruct_strat,
604631
prefer_attrib_converters=prefer_attrib_converters,
632+
extended_validation=extended_validation,
605633
)
606634
self.omit_if_default = omit_if_default
607635
self.forbid_extra_keys = forbid_extra_keys
@@ -742,6 +770,7 @@ def gen_structure_attrs_fromdict(self, cl: Type[T]) -> T:
742770
self,
743771
_cattrs_forbid_extra_keys=self.forbid_extra_keys,
744772
_cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
773+
_cattrs_extended_validation=self.extended_validation,
745774
**attrib_overrides,
746775
)
747776
# only direct dispatch so that subclasses get separately generated

src/cattr/errors.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
from typing import Type
1+
from cattrs.errors import StructureHandlerNotFoundError
22

3-
4-
class StructureHandlerNotFoundError(Exception):
5-
"""Error raised when structuring cannot find a handler for converting inputs into :attr:`type_`."""
6-
7-
def __init__(self, message: str, type_: Type) -> None:
8-
super().__init__(message)
9-
self.type_ = type_
3+
__all__ = ["StructureHandlerNotFoundError"]

0 commit comments

Comments
 (0)