Skip to content

Commit 33c8fe6

Browse files
committed
Rework exception classes, more tests
1 parent 6721395 commit 33c8fe6

File tree

6 files changed

+319
-762
lines changed

6 files changed

+319
-762
lines changed

src/cattr/_compat.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ def get_origin(cl):
3434
else:
3535
from typing import Protocol, get_args, get_origin # NOQA
3636

37+
if "ExceptionGroup" not in __builtins__:
38+
from exceptiongroup import ExceptionGroup
39+
else:
40+
ExceptionGroup = ExceptionGroup
41+
3742

3843
def has(cls):
3944
return hasattr(cls, "__attrs_attrs__") or hasattr(

src/cattr/converters.py

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -479,52 +479,55 @@ def structure_attrs_fromdict(
479479

480480
def _structure_list(self, obj, cl):
481481
"""Convert an iterable to a potentially generic list."""
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)
482+
if is_bare(cl) or cl.__args__[0] is Any:
483+
res = [e for e in obj]
484+
else:
485+
elem_type = cl.__args__[0]
486+
handler = self._structure_func.dispatch(elem_type)
487+
if self.extended_validation:
488+
errors = []
489489
res = []
490490
ix = 0 # Avoid `enumerate` for performance.
491491
for e in obj:
492492
try:
493493
res.append(handler(e, elem_type))
494494
except Exception as e:
495-
errors[ix] = e
495+
e.__note__ = f"Structuring iterable @ index {ix}"
496+
errors.append(e)
496497
finally:
497498
ix += 1
498-
if errors:
499-
raise IterableValidationError(errors)
500-
else:
501-
if is_bare(cl) or cl.__args__[0] is Any:
502-
res = [e for e in obj]
499+
if errors:
500+
raise IterableValidationError(f"While structuring {cl.__name__}", errors, cl)
503501
else:
504-
elem_type = cl.__args__[0]
505-
handler = self._structure_func.dispatch(elem_type)
506502
res = [handler(e, elem_type) for e in obj]
507503
return res
508504

509-
def _structure_set(self, obj, cl):
505+
def _structure_set(self, obj, cl, structure_to=set):
510506
"""Convert an iterable into a potentially generic set."""
511507
if is_bare(cl) or cl.__args__[0] is Any:
512-
return set(obj)
508+
return structure_to(obj)
509+
elem_type = cl.__args__[0]
510+
handler = self._structure_func.dispatch(elem_type)
511+
if self.extended_validation:
512+
errors = []
513+
res = set()
514+
for e in obj:
515+
try:
516+
res.add(handler(e, elem_type))
517+
except Exception as exc:
518+
exc.__note__ = f"Structuring {structure_to.__name__} @ element {e!r}"
519+
errors.append(exc)
520+
if errors:
521+
raise IterableValidationError(f"While structuring {cl.__name__}", errors, cl)
522+
return res if structure_to is set else structure_to(res)
523+
elif structure_to is set:
524+
return {handler(e, elem_type) for e in obj}
513525
else:
514-
elem_type = cl.__args__[0]
515-
return {
516-
self._structure_func.dispatch(elem_type)(e, elem_type)
517-
for e in obj
518-
}
526+
return structure_to([handler(e, elem_type) for e in obj])
519527

520528
def _structure_frozenset(self, obj, cl):
521529
"""Convert an iterable into a potentially generic frozenset."""
522-
if is_bare(cl) or cl.__args__[0] is Any:
523-
return frozenset(obj)
524-
else:
525-
elem_type = cl.__args__[0]
526-
dispatch = self._structure_func.dispatch
527-
return frozenset(dispatch(elem_type)(e, elem_type) for e in obj)
530+
return self._structure_set(obj, cl, structure_to=frozenset)
528531

529532
def _structure_dict(self, obj, cl):
530533
"""Convert a mapping into a potentially generic dict."""
@@ -562,7 +565,7 @@ def _structure_union(self, obj, union):
562565
return handler(obj, union)
563566

564567
def _structure_tuple(self, obj, tup: Type[T]):
565-
"""Deal with converting to a tuple."""
568+
"""Deal with structuring into a tuple."""
566569
if tup in (Tuple, tuple):
567570
tup_params = None
568571
else:
@@ -575,7 +578,20 @@ def _structure_tuple(self, obj, tup: Type[T]):
575578
# We're dealing with a homogenous tuple, Tuple[int, ...]
576579
tup_type = tup_params[0]
577580
conv = self._structure_func.dispatch(tup_type)
578-
return tuple(conv(e, tup_type) for e in obj)
581+
if self.extended_validation:
582+
errors = []
583+
res = []
584+
for ix, e in enumerate(obj):
585+
try:
586+
res.append(conv(e, tup_type))
587+
except Exception as exc:
588+
exc.__note__ = f"Structuring tuple @ index {ix}"
589+
errors.append(exc)
590+
if errors:
591+
raise IterableValidationError(f"While structuring {tup.__name__}", errors, tup)
592+
return tuple(res)
593+
else:
594+
return tuple(conv(e, tup_type) for e in obj)
579595
else:
580596
# We're dealing with a heterogenous tuple.
581597
return tuple(

0 commit comments

Comments
 (0)