Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 4 additions & 2 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
([#577](https://github.com/python-attrs/cattrs/pull/577))
- Expose {func}`cattrs.cols.mapping_unstructure_factory` through {mod}`cattrs.cols`.
- Some `defaultdicts` are now [supported by default](https://catt.rs/en/latest/defaulthooks.html#defaultdicts), and
{func}`cattrs.cols.is_defaultdict`{func} and `cattrs.cols.defaultdict_structure_factory` are exposed through {mod}`cattrs.cols`.
{func}`cattrs.cols.is_defaultdict` and {func}`cattrs.cols.defaultdict_structure_factory` are exposed through {mod}`cattrs.cols`.
([#519](https://github.com/python-attrs/cattrs/issues/519) [#588](https://github.com/python-attrs/cattrs/pull/588))
- Many preconf converters (_bson_, stdlib JSON, _cbor2_, _msgpack_, _msgspec_, _orjson_, _ujson_) skip unstructuring `int` and `str` enums,
leaving them to the underlying libraries to handle with greater efficiency.
Expand All @@ -29,7 +29,9 @@ 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))
- Replace `cattrs.gen.MappingStructureFn` with `cattrs.SimpleStructureHook[In, T]`.
- 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 {class}`cattrs.SimpleStructureHook`.
- Python 3.13 is now supported.
([#543](https://github.com/python-attrs/cattrs/pull/543) [#547](https://github.com/python-attrs/cattrs/issues/547))
- Python 3.8 is no longer supported, as it is end-of-life. Use previous versions on this Python version.
Expand Down
2 changes: 1 addition & 1 deletion docs/defaulthooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ Generic TypedDicts work on Python 3.11 and later, since that is the first Python

[`typing.Required` and `typing.NotRequired`](https://peps.python.org/pep-0655/) are supported.

[Similar to _attrs_ classes](customizing.md#using-cattrsgen-generators), un/structuring can be customized using {meth}`cattrs.gen.typeddicts.make_dict_structure_fn` and {meth}`cattrs.gen.typeddicts.make_dict_unstructure_fn`.
[Similar to _attrs_ classes](customizing.md#using-cattrsgen-hook-factories), un/structuring can be customized using {meth}`cattrs.gen.typeddicts.make_dict_structure_fn` and {meth}`cattrs.gen.typeddicts.make_dict_unstructure_fn`.

```{doctest}
>>> from typing import TypedDict
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 @ $"]
Loading