Skip to content

fix: use __notes__ instead of __note__ #303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In essence, ExceptionGroups are trees of exceptions.
When un/structuring a class, `cattrs` will gather any exceptions on a field-by-field basis and raise them as a ``cattrs.ClassValidationError``, which is a subclass of ``BaseValidationError``.
When structuring sequences and mappings, `cattrs` will gather any exceptions on a key- or index-basis and raise them as a ``cattrs.IterableValidationError``, which is a subclass of ``BaseValidationError``.

The exceptions will also have their ``__note__`` attributes set, as per `PEP 678`_, showing the field, key or index for each inner exception.
The exceptions will also have their ``__notes__`` attributes set, as per `PEP 678`_, showing the field, key or index for each inner exception.

A simple example involving a class containing a list and a dictionary:

Expand Down
17 changes: 10 additions & 7 deletions src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@ def _structure_list(self, obj, cl):
try:
res.append(handler(e, elem_type))
except Exception as e:
e.__note__ = f"Structuring {cl} @ index {ix}"
msg = f"Structuring {cl} @ index {ix}"
e.__notes__ = getattr(e, "__notes__", ()) + (msg,)
errors.append(e)
finally:
ix += 1
Expand All @@ -495,9 +496,8 @@ def _structure_set(self, obj, cl, structure_to=set):
try:
res.add(handler(e, elem_type))
except Exception as exc:
exc.__note__ = (
f"Structuring {structure_to.__name__} @ element {e!r}"
)
msg = f"Structuring {structure_to.__name__} @ element {e!r}"
exc.__notes__ = getattr(e, "__notes__", ()) + (msg,)
errors.append(exc)
if errors:
raise IterableValidationError(f"While structuring {cl!r}", errors, cl)
Expand Down Expand Up @@ -564,7 +564,8 @@ def _structure_tuple(self, obj, tup: Type[T]) -> T:
try:
res.append(conv(e, tup_type))
except Exception as exc:
exc.__note__ = f"Structuring {tup} @ index {ix}"
msg = f"Structuring {tup} @ index {ix}"
exc.__notes__ = getattr(e, "__notes__", ()) + (msg,)
errors.append(exc)
if errors:
raise IterableValidationError(
Expand All @@ -591,14 +592,16 @@ def _structure_tuple(self, obj, tup: Type[T]) -> T:
conv = self._structure_func.dispatch(t)
res.append(conv(e, t))
except Exception as exc:
exc.__note__ = f"Structuring {tup} @ index {ix}"
msg = f"Structuring {tup} @ index {ix}"
exc.__notes__ = getattr(e, "__notes__", ()) + (msg,)
errors.append(exc)
if len(res) < exp_len:
problem = "Not enough" if len(res) < len(tup_params) else "Too many"
exc = ValueError(
f"{problem} values in {obj!r} to structure as {tup!r}"
)
exc.__note__ = f"Structuring {tup}"
msg = f"Structuring {tup}"
exc.__notes__ = getattr(e, "__notes__", ()) + (msg,)
errors.append(exc)
if errors:
raise IterableValidationError(
Expand Down
6 changes: 3 additions & 3 deletions src/cattrs/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def make_dict_structure_fn(
lines.append(f"{i}except Exception as e:")
i = f"{i} "
lines.append(
f"{i}e.__note__ = 'Structuring class {cl.__qualname__} @ attribute {an}'"
f"{i}e.__notes__ = getattr(e, '__notes__', ()) + ('Structuring class {cl.__qualname__} @ attribute {an}',)"
)
lines.append(f"{i}errors.append(e)")

Expand Down Expand Up @@ -703,7 +703,7 @@ def make_mapping_structure_fn(
lines.append(f" value = {v_s}")
lines.append(" except Exception as e:")
lines.append(
" e.__note__ = 'Structuring mapping value @ key ' + repr(k)"
" e.__notes__ = getattr(e, '__notes__', ()) + ('Structuring mapping value @ key ' + repr(k),)"
)
lines.append(" errors.append(e)")
lines.append(" continue")
Expand All @@ -712,7 +712,7 @@ def make_mapping_structure_fn(
lines.append(" res[key] = value")
lines.append(" except Exception as e:")
lines.append(
" e.__note__ = 'Structuring mapping key @ key ' + repr(k)"
" e.__notes__ = getattr(e, '__notes__', ()) + ('Structuring mapping key @ key ' + repr(k),)"
)
lines.append(" errors.append(e)")
lines.append(" if errors:")
Expand Down
44 changes: 25 additions & 19 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ class Test:
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert (
exc.value.exceptions[0].__note__
== "Structuring class test_class_validation.<locals>.Test @ attribute a"
assert exc.value.exceptions[0].__notes__ == (
"Structuring class test_class_validation.<locals>.Test @ attribute a",
)

assert repr(exc.value.exceptions[1]) == repr(KeyError("c"))
assert (
exc.value.exceptions[1].__note__
== "Structuring class test_class_validation.<locals>.Test @ attribute c"
assert exc.value.exceptions[1].__notes__ == (
"Structuring class test_class_validation.<locals>.Test @ attribute c",
)


Expand Down Expand Up @@ -66,12 +64,16 @@ def test_list_validation():
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert exc.value.exceptions[0].__note__ == "Structuring typing.List[int] @ index 2"
assert exc.value.exceptions[0].__notes__ == (
"Structuring typing.List[int] @ index 2",
)

assert repr(exc.value.exceptions[1]) == repr(
ValueError("invalid literal for int() with base 10: 'c'")
)
assert exc.value.exceptions[1].__note__ == "Structuring typing.List[int] @ index 4"
assert exc.value.exceptions[1].__notes__ == (
"Structuring typing.List[int] @ index 4",
)


@given(...)
Expand All @@ -86,12 +88,16 @@ def test_mapping_validation(detailed_validation: bool):
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'b'")
)
assert exc.value.exceptions[0].__note__ == "Structuring mapping value @ key '2'"
assert exc.value.exceptions[0].__notes__ == (
"Structuring mapping value @ key '2'",
)

assert repr(exc.value.exceptions[1]) == repr(
ValueError("invalid literal for int() with base 10: 'c'")
)
assert exc.value.exceptions[1].__note__ == "Structuring mapping key @ key 'c'"
assert exc.value.exceptions[1].__notes__ == (
"Structuring mapping key @ key 'c'",
)
else:
with pytest.raises(ValueError):
c.structure({"1": 1, "2": "b", "c": 3}, Dict[int, int])
Expand All @@ -109,7 +115,9 @@ def test_counter_validation(detailed_validation: bool):
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'b'")
)
assert exc.value.exceptions[0].__note__ == "Structuring mapping value @ key 'b'"
assert exc.value.exceptions[0].__notes__ == (
"Structuring mapping value @ key 'b'",
)

else:
with pytest.raises(ValueError):
Expand All @@ -126,7 +134,7 @@ def test_set_validation():
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert exc.value.exceptions[0].__note__ == "Structuring set @ element 'a'"
assert exc.value.exceptions[0].__notes__ == ("Structuring set @ element 'a'",)


def test_frozenset_validation():
Expand All @@ -139,7 +147,7 @@ def test_frozenset_validation():
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert exc.value.exceptions[0].__note__ == "Structuring frozenset @ element 'a'"
assert exc.value.exceptions[0].__notes__ == ("Structuring frozenset @ element 'a'",)


def test_homo_tuple_validation():
Expand All @@ -152,9 +160,8 @@ def test_homo_tuple_validation():
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert (
exc.value.exceptions[0].__note__
== "Structuring typing.Tuple[int, ...] @ index 2"
assert exc.value.exceptions[0].__notes__ == (
"Structuring typing.Tuple[int, ...] @ index 2",
)


Expand All @@ -168,7 +175,6 @@ def test_hetero_tuple_validation():
assert repr(exc.value.exceptions[0]) == repr(
ValueError("invalid literal for int() with base 10: 'a'")
)
assert (
exc.value.exceptions[0].__note__
== "Structuring typing.Tuple[int, int, int] @ index 2"
assert exc.value.exceptions[0].__notes__ == (
"Structuring typing.Tuple[int, int, int] @ index 2",
)