Skip to content

Commit 2ec13dd

Browse files
authored
Fix structuring of nested generics, add test (#688)
* Fix structuring of nested generics, add test * Black lint, changelog entry, better test docstring
1 parent 29f3a4e commit 2ec13dd

File tree

3 files changed

+31
-2
lines changed

3 files changed

+31
-2
lines changed

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ The third number is for emergencies when we need to start branches for older rel
1111

1212
Our backwards-compatibility policy can be found [here](https://github.com/python-attrs/cattrs/blob/main/.github/SECURITY.md).
1313

14+
## 25.4.0 (UNRELEASED)
15+
16+
- Fix structuring of nested generic classes with stringified annotations.
17+
([#688](https://github.com/python-attrs/cattrs/pull/688))
18+
1419
## 25.3.0 (2025-10-07)
1520

1621
- **Potentially breaking**: [Abstract sets](https://docs.python.org/3/library/collections.abc.html#collections.abc.Set) are now structured into frozensets.

src/cattrs/converters.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,10 +1303,11 @@ def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]:
13031303
def gen_structure_attrs_fromdict(
13041304
self, cl: type[T]
13051305
) -> Callable[[Mapping[str, Any], Any], T]:
1306-
attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl)
1306+
origin = get_origin(cl)
1307+
attribs = fields(origin or cl if is_generic(cl) else cl)
13071308
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
13081309
# PEP 563 annotations - need to be resolved.
1309-
resolve_types(cl)
1310+
resolve_types(origin or cl)
13101311
attrib_overrides = {
13111312
a.name: self.type_overrides[a.type]
13121313
for a in attribs

tests/test_generics_nested.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Tests un/structure of nested generic classes (stringified only)"""
2+
3+
from __future__ import annotations
4+
5+
from typing import Generic, TypeVar
6+
7+
from attrs import define
8+
9+
T = TypeVar("T")
10+
11+
12+
def test_structure_nested_roundtrip(genconverter):
13+
@define(auto_attribs=True)
14+
class Inner:
15+
value: int
16+
17+
@define(auto_attribs=True)
18+
class Container(Generic[T]):
19+
data: T
20+
21+
raw = {"data": {"value": 42}}
22+
structured = genconverter.structure(raw, Container[Inner])
23+
assert genconverter.unstructure(structured, Container[Inner]) == raw

0 commit comments

Comments
 (0)