diff --git a/HISTORY.md b/HISTORY.md index 4768967a..2eed1dce 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,6 +11,11 @@ The third number is for emergencies when we need to start branches for older rel Our backwards-compatibility policy can be found [here](https://github.com/python-attrs/cattrs/blob/main/.github/SECURITY.md). +## 25.4.0 (UNRELEASED) + +- Fix structuring of nested generic classes with stringified annotations. + ([#688](https://github.com/python-attrs/cattrs/pull/688)) + ## 25.3.0 (2025-10-07) - **Potentially breaking**: [Abstract sets](https://docs.python.org/3/library/collections.abc.html#collections.abc.Set) are now structured into frozensets. diff --git a/src/cattrs/converters.py b/src/cattrs/converters.py index 08d7fc56..13f63ee1 100644 --- a/src/cattrs/converters.py +++ b/src/cattrs/converters.py @@ -1303,10 +1303,11 @@ def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]: def gen_structure_attrs_fromdict( self, cl: type[T] ) -> Callable[[Mapping[str, Any], Any], T]: - attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl) + origin = get_origin(cl) + attribs = fields(origin or cl if is_generic(cl) else 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_nested.py b/tests/test_generics_nested.py new file mode 100644 index 00000000..1e0da3a8 --- /dev/null +++ b/tests/test_generics_nested.py @@ -0,0 +1,23 @@ +"""Tests un/structure of nested generic classes (stringified only)""" + +from __future__ import annotations + +from typing import Generic, TypeVar + +from attrs import define + +T = TypeVar("T") + + +def test_structure_nested_roundtrip(genconverter): + @define(auto_attribs=True) + class Inner: + value: int + + @define(auto_attribs=True) + class Container(Generic[T]): + data: T + + raw = {"data": {"value": 42}} + structured = genconverter.structure(raw, Container[Inner]) + assert genconverter.unstructure(structured, Container[Inner]) == raw