Skip to content

Commit

Permalink
typeddicts: raise proper error on invalid input
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinche committed Jan 6, 2025
1 parent e2bdc84 commit 1bc458b
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 3 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Our backwards-compatibility policy can be found [here](/~https://github.com/python
([#598](/~https://github.com/python-attrs/cattrs/pull/598))
- Preconf converters now handle dictionaries with literal keys properly.
([#599](/~https://github.com/python-attrs/cattrs/pull/599))
- Structuring TypedDicts from invalid inputs now properly raises a {class}`ClassValidationError`.
([#615](/~https://github.com/python-attrs/cattrs/issues/615) [#616](/~https://github.com/python-attrs/cattrs/pull/616))
- Replace `cattrs.gen.MappingStructureFn` with `cattrs.SimpleStructureHook[In, T]`.
- Python 3.13 is now supported.
([#543](/~https://github.com/python-attrs/cattrs/pull/543) [#547](/~https://github.com/python-attrs/cattrs/issues/547))
Expand Down
12 changes: 10 additions & 2 deletions src/cattrs/gen/typeddicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,16 @@ def make_dict_structure_fn(
globs["__c_a"] = allowed_fields
globs["__c_feke"] = ForbiddenExtraKeysError

lines.append(" res = o.copy()")

if _cattrs_detailed_validation:
# When running under detailed validation, be extra careful about copying
# so that the correct error is raised if the input isn't a dict.
lines.append(" try:")
lines.append(" res = o.copy()")
lines.append(" except Exception as exc:")
lines.append(
f" raise __c_cve('While structuring ' + {cl.__name__!r}, [exc], __cl)"
)

lines.append(" errors = []")
internal_arg_parts["__c_cve"] = ClassValidationError
internal_arg_parts["__c_avn"] = AttributeValidationNote
Expand Down Expand Up @@ -383,6 +390,7 @@ def make_dict_structure_fn(
f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)"
)
else:
lines.append(" res = o.copy()")
non_required = []

# The first loop deals with required args.
Expand Down
11 changes: 10 additions & 1 deletion tests/test_typeddicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pytest import raises
from typing_extensions import NotRequired, Required

from cattrs import BaseConverter, Converter
from cattrs import BaseConverter, Converter, transform_error
from cattrs._compat import ExtensionsTypedDict, get_notrequired_base, is_generic
from cattrs.errors import (
ClassValidationError,
Expand Down Expand Up @@ -509,3 +509,12 @@ class A(ExtensionsTypedDict):

assert converter.unstructure({"a": 10, "b": 10}, A) == {"a": 1, "b": 2}
assert converter.structure({"a": 10, "b": 10}, A) == {"a": 1, "b": 2}


def test_nondict_input():
"""Trying to structure typeddict from a non-dict raises the proper exception."""
converter = Converter(detailed_validation=True)
with raises(ClassValidationError) as exc:
converter.structure(1, TypedDictA)

assert transform_error(exc.value) == ["expected a mapping @ $"]

0 comments on commit 1bc458b

Please sign in to comment.