Skip to content

Commit ef206d2

Browse files
committed
Error handling coverage
1 parent a016e78 commit ef206d2

4 files changed

Lines changed: 37 additions & 23 deletions

File tree

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ can now be used as decorators and have gained new features.
3131
([#472](https://github.com/python-attrs/cattrs/pull/472))
3232
- The default union handler now also handles dataclasses.
3333
([#426](https://github.com/python-attrs/cattrs/issues/426) [#477](https://github.com/python-attrs/cattrs/pull/477))
34+
- **Potentially breaking**: `IterableValidationError`s now require their subexceptions to have appropriate notes attached.
35+
This was always the case internally in _cattrs_, but is now required of errors produced outside too.
3436
- Add support for [PEP 695](https://peps.python.org/pep-0695/) type aliases.
3537
([#452](https://github.com/python-attrs/cattrs/pull/452))
3638
- Add support for [PEP 696](https://peps.python.org/pep-0696/) `TypeVar`s with defaults.

src/cattrs/errors.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,24 @@ def __getnewargs__(self) -> Tuple[str, Union[int, str], Any]:
4545

4646

4747
class IterableValidationError(BaseValidationError):
48-
"""Raised when structuring an iterable."""
48+
"""Raised when structuring an iterable.
4949
50-
def group_exceptions(
51-
self,
52-
) -> Tuple[List[Tuple[Exception, IterableValidationNote]], List[Exception]]:
53-
"""Split the exceptions into two groups: with and without validation notes."""
50+
If instantiating this error manually (outside of cattrs), ensure every
51+
subexception has an appropriate IterableValidationNote note in its notes.
52+
"""
53+
54+
def group_exceptions(self) -> List[Tuple[Exception, IterableValidationNote]]:
55+
"""Group up the subexceptions alongside their IV notes."""
5456
excs_with_notes = []
55-
other_excs = []
5657
for subexc in self.exceptions:
57-
if hasattr(subexc, "__notes__"):
58-
for note in subexc.__notes__:
59-
if note.__class__ is IterableValidationNote:
60-
excs_with_notes.append((subexc, note))
61-
break
62-
else:
63-
other_excs.append(subexc)
58+
for note in subexc.__notes__:
59+
if note.__class__ is IterableValidationNote:
60+
excs_with_notes.append((subexc, note))
61+
break
6462
else:
65-
other_excs.append(subexc)
63+
raise AttributeError("Subexceptions require notes")
6664

67-
return excs_with_notes, other_excs
65+
return excs_with_notes
6866

6967

7068
class AttributeValidationNote(str):

src/cattrs/v/__init__.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,12 @@ def transform_error(
9999
"""
100100
errors: List[str] = []
101101
if isinstance(exc, IterableValidationError):
102-
with_notes, without = exc.group_exceptions()
103-
for exc, note in with_notes:
102+
for e, note in exc.group_exceptions():
104103
p = f"{path}[{note.index!r}]"
105-
if isinstance(exc, (ClassValidationError, IterableValidationError)):
106-
errors.extend(transform_error(exc, p, format_exception))
104+
if isinstance(e, (ClassValidationError, IterableValidationError)):
105+
errors.extend(transform_error(e, p, format_exception))
107106
else:
108-
errors.append(f"{format_exception(exc, note.type)} @ {p}")
109-
for exc in without:
110-
errors.append(f"{format_exception(exc, None)} @ {path}")
107+
errors.append(f"{format_exception(e, note.type)} @ {p}")
111108
elif isinstance(exc, ClassValidationError):
112109
with_notes, without = exc.group_exceptions()
113110
for exc, note in with_notes:

tests/v/test_v.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from attrs import Factory, define, field
1414
from pytest import raises
1515

16-
from cattrs import Converter, transform_error
16+
from cattrs import Converter, IterableValidationError, transform_error
1717
from cattrs._compat import Mapping, TypedDict
1818
from cattrs.gen import make_dict_structure_fn
1919
from cattrs.v import format_exception
@@ -352,3 +352,20 @@ class E(TypedDict):
352352
def test_other_errors():
353353
"""Errors without explicit support transform predictably."""
354354
assert format_exception(IndexError("Test"), List[int]) == "unknown error (Test)"
355+
356+
357+
def test_iterable_val_no_note():
358+
"""`IterableValidationErrors` require subexceptions with notes."""
359+
with raises(AttributeError):
360+
IterableValidationError("Test", [RuntimeError()], List[str]).group_exceptions()
361+
362+
r = RuntimeError()
363+
r.__notes__ = ["test"]
364+
with raises(AttributeError):
365+
IterableValidationError("Test", [r], List[str]).group_exceptions()
366+
367+
368+
def test_typeerror_formatting():
369+
"""`format_exception` works with non-iteration TypeErrors."""
370+
exc = TypeError("exception")
371+
assert format_exception(exc, None) == "invalid type (exception)"

0 commit comments

Comments
 (0)