Skip to content

Commit

Permalink
For forbid_extra_keys raise custom ForbiddenExtraKeyError ins…
Browse files Browse the repository at this point in the history
…tead of generic ``Exception``.
  • Loading branch information
raabf committed Mar 25, 2022
1 parent 5675952 commit b20eab5
Show file tree
Hide file tree
Showing 6 changed files with 15 additions and 8 deletions.
3 changes: 2 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ History
(`#231 <https://github.com/python-attrs/cattrs/pull/231>`_)
* Fix unstructuring all tuples - unannotated, variable-length, homogenous and heterogenous - to `list`.
(`#226 <https://github.com/python-attrs/cattrs/issues/226>`_)

* For ``forbid_extra_keys`` raise custom ``ForbiddenExtraKeyError`` instead of generic ``Exception``.
(`#255 <https://github.com/python-attrs/cattrs/pull/225>`_)

1.10.0 (2022-01-04)
-------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/customizing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ creating structure hooks with ``make_dict_structure_fn``.
>>> c.structure({"nummber": 2}, TestClass)
Traceback (most recent call last):
...
Exception: Extra fields in constructor for TestClass: nummber
ForbiddenExtraKeyError: Extra fields in constructor for TestClass: nummber
>>> hook = make_dict_structure_fn(TestClass, c, _cattrs_forbid_extra_keys=False)
>>> c.register_structure_hook(TestClass, hook)
>>> c.structure({"nummber": 2}, TestClass)
Expand Down
2 changes: 1 addition & 1 deletion docs/structuring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ Here's a small example showing how to use factory hooks to apply the `forbid_ext
>>> c.structure({"an_int": 1, "else": 2}, E)
Traceback (most recent call last):
...
Exception: Extra fields in constructor for E: else
ForbiddenExtraKeyError: Extra fields in constructor for E: else
A complex use case for hook factories is described over at :ref:`Using factory hooks`.
Expand Down
4 changes: 4 additions & 0 deletions src/cattrs/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ class ClassValidationError(BaseValidationError):
"""Raised when validating a class if any attributes are invalid."""

pass


class ForbiddenExtraKeyError(Exception):
"""Raised when `forbid_extra_keys` is activated and such an extra key is detected during structuring."""
3 changes: 2 additions & 1 deletion src/cattrs/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,11 @@ def make_dict_structure_fn(

if _cattrs_forbid_extra_keys:
globs["__c_a"] = allowed_fields
globs["ForbiddenExtraKeyError"] = ForbiddenExtraKeyError
post_lines += [
" unknown_fields = set(o.keys()) - __c_a",
" if unknown_fields:",
" raise Exception(",
" raise ForbiddenExtraKeyError(",
f" 'Extra fields in constructor for {cl_name}: ' + ', '.join(unknown_fields)"
" )",
]
Expand Down
9 changes: 5 additions & 4 deletions tests/metadata/test_genconverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from cattr import GenConverter as Converter
from cattr import UnstructureStrategy
from cattr._compat import is_py39_plus, is_py310_plus
from cattrs.errors import ForbiddenExtraKeyError
from cattr.gen import make_dict_structure_fn, override

from . import (
Expand Down Expand Up @@ -87,7 +88,7 @@ def test_forbid_extra_keys(cls_and_vals):
while bad_key in unstructured:
bad_key += "A"
unstructured[bad_key] = 1
with pytest.raises(Exception):
with pytest.raises(ForbiddenExtraKeyError):
converter.structure(unstructured, cl)


Expand All @@ -102,7 +103,7 @@ def test_forbid_extra_keys_defaults(attr_and_vals):
inst = cl()
unstructured = converter.unstructure(inst)
unstructured["aa"] = unstructured.pop("a")
with pytest.raises(Exception):
with pytest.raises(ForbiddenExtraKeyError):
converter.structure(unstructured, cl)


Expand All @@ -122,15 +123,15 @@ class A:
converter.structure(unstructured, A)
# if we break it in the subclass, we need it to raise
unstructured["c"]["aa"] = 5
with pytest.raises(Exception):
with pytest.raises(ForbiddenExtraKeyError):
converter.structure(unstructured, A)
# we can "fix" that by disabling forbid_extra_keys on the subclass
hook = make_dict_structure_fn(C, converter, _cattrs_forbid_extra_keys=False)
converter.register_structure_hook(C, hook)
converter.structure(unstructured, A)
# but we should still raise at the top level
unstructured["b"] = 6
with pytest.raises(Exception):
with pytest.raises(ForbiddenExtraKeyError):
converter.structure(unstructured, A)


Expand Down

0 comments on commit b20eab5

Please sign in to comment.