diff --git a/HISTORY.md b/HISTORY.md index 2c56c85d..ea27e568 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python and {func}`cattrs.gen.typeddicts.make_dict_structure_fn` will use the value for the `use_alias` parameter from the given converter by default now. If you're using these functions directly, the old behavior can be restored by passing in the desired value directly. ([#596](https://github.com/python-attrs/cattrs/issues/596) [#660](https://github.com/python-attrs/cattrs/pull/660)) +- Fix unstructuring of generic classes with stringified annotations. + ([#661](https://github.com/python-attrs/cattrs/issues/661) [#662](https://github.com/python-attrs/cattrs/issues/662)) ## 25.1.1 (2025-06-04) diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index aa895552..ee04e653 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -1252,7 +1252,7 @@ def gen_unstructure_attrs_fromdict( attribs = fields(origin or cl) if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): # PEP 563 annotations - need to be resolved. - resolve_types(cl) + resolve_types(origin or cl) attrib_overrides = { a.name: self.type_overrides[a.type] for a in attribs diff --git a/tests/test_generics_649.py b/tests/test_generics_649.py new file mode 100644 index 00000000..57e829cb --- /dev/null +++ b/tests/test_generics_649.py @@ -0,0 +1,25 @@ +"""Tests for PEP 649 (Deferred Evaluation Of Annotations Using Descriptors).""" + +from __future__ import annotations + +from typing import Generic, TypeVar + +from attrs import define + +from cattrs import Converter + +T = TypeVar("T") + + +@define +class GenericClass(Generic[T]): + t: T + + +def test_generics_with_stringified_annotations(): + """Type resolution works with stringified annotations.""" + converter = Converter() + inst = GenericClass(42) + dct = converter.unstructure(inst, unstructure_as=GenericClass[int]) + assert dct == {"t": 42} + assert converter.structure(dct, GenericClass[int])