diff --git a/HISTORY.rst b/HISTORY.rst index f048f1b7..e4f7673b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -18,7 +18,8 @@ History (`#231 `_) * Fix unstructuring all tuples - unannotated, variable-length, homogenous and heterogenous - to `list`. (`#226 `_) - +* For ``forbid_extra_keys`` raise custom ``ForbiddenExtraKeyError`` instead of generic ``Exception``. + (`#255 `_) 1.10.0 (2022-01-04) ------------------- diff --git a/docs/customizing.rst b/docs/customizing.rst index f0e7b4ee..e9927f5b 100644 --- a/docs/customizing.rst +++ b/docs/customizing.rst @@ -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) diff --git a/docs/structuring.rst b/docs/structuring.rst index dfa9489a..a16ed490 100644 --- a/docs/structuring.rst +++ b/docs/structuring.rst @@ -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`. diff --git a/src/cattrs/errors.py b/src/cattrs/errors.py index a85af868..7e588737 100644 --- a/src/cattrs/errors.py +++ b/src/cattrs/errors.py @@ -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.""" diff --git a/src/cattrs/gen.py b/src/cattrs/gen.py index 3027cf30..c0456ddb 100644 --- a/src/cattrs/gen.py +++ b/src/cattrs/gen.py @@ -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)" " )", ] diff --git a/tests/metadata/test_genconverter.py b/tests/metadata/test_genconverter.py index 419aaf59..fe21e63c 100644 --- a/tests/metadata/test_genconverter.py +++ b/tests/metadata/test_genconverter.py @@ -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 ( @@ -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) @@ -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) @@ -122,7 +123,7 @@ 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) @@ -130,7 +131,7 @@ class A: 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)