Skip to content

Fix structuring of nested generics, add test#688

Merged
Tinche merged 2 commits intopython-attrs:mainfrom
calebj:fix_nested_generics
Oct 16, 2025
Merged

Fix structuring of nested generics, add test#688
Tinche merged 2 commits intopython-attrs:mainfrom
calebj:fix_nested_generics

Conversation

@calebj
Copy link
Contributor

@calebj calebj commented Oct 15, 2025

This mirrors #662 to allow nested generics for structuring nested generic classes. I encountered this issue when composing generic classes describing a REST API's JSON schema. It only seems to happen with string annotations.

Without the patch, the included tests will show TypeErrors similar to:

  File "cattrs/tests/test_generics_nested_string.py", line 23, in test_structure_nested_roundtrip
    structured = genconverter.structure(raw, Container[Inner])
  File "cattrs/src/cattrs/converters.py", line 589, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^
  File "cattrs/src/cattrs/dispatch.py", line 133, in dispatch_without_caching
    res = self._function_dispatch.dispatch(typ)
  File "cattrs/src/cattrs/dispatch.py", line 76, in dispatch
    return handler(typ)
  File "cattrs/src/cattrs/converters.py", line 1309, in gen_structure_attrs_fromdict
    resolve_types(cl)
    ~~~~~~~~~~~~~^^^^
  File "cattrs/.venv/lib/python3.13/site-packages/attr/_funcs.py", line 487, in resolve_types
    hints = typing.get_type_hints(cls, **kwargs)
  File "/usr/lib/python3.13/typing.py", line 2484, in get_type_hints
    raise TypeError('{!r} is not a module, class, method, '
                    'or function.'.format(obj))
TypeError: tests.test_generics_nested_string.test_structure_nested_roundtrip.<locals>.Container[tests.test_generics_nested_string.test_structure_nested_roundtrip.<locals>.Inner] is not a module, class, method, or function.

Minimal reproducing example:

from __future__ import annotations

from typing import Generic, TypeVar

from attrs import define
from cattrs import Converter

T = TypeVar('T')

@define(auto_attribs=True)
class Inner:
    value: int

@define(auto_attribs=True)
class Container(Generic[T]):
    data: T

Converter().structure({'data': {'value': 42}}, Container[Inner])

@Tinche
Copy link
Member

Tinche commented Oct 15, 2025

Good catch!

Please add a small docstring to the test and a changelog entry.

@Tinche
Copy link
Member

Tinche commented Oct 16, 2025

Great job, thanks!

@Tinche Tinche merged commit 2ec13dd into python-attrs:main Oct 16, 2025
13 checks passed
@calebj calebj deleted the fix_nested_generics branch October 16, 2025 16:03
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Feb 22, 2026
## 26.1.0 (2026-02-18)

- Add the {mod}`tomllib <cattrs.preconf.tomllib>` preconf converter.
  See [here](https://catt.rs/en/latest/preconf.html#tomllib) for details.
  ([#716](python-attrs/cattrs#716))
- Customizing un/structuring of _attrs_ classes, dataclasses, TypedDicts and dict NamedTuples is now possible by using `Annotated[T, override()]` on fields.
  See [here](https://catt.rs/en/stable/customizing.html#using-typing-annotated-t-override) for more details.
  ([#717](python-attrs/cattrs#717))
- Fix structuring of nested generic classes with stringified annotations.
  ([#688](python-attrs/cattrs#688))
- Python 3.9 is no longer supported, as it is end-of-life. Use previous versions on this Python version.
  ([#698](python-attrs/cattrs#698))
- Apply the attrs converter to the default value before checking if it is equal to the attribute's value, when `omit_if_default` is true and an attrs converter is specified.
  ([#696](python-attrs/cattrs#696))
- Use the optional `_value_` type hint to structure and unstructure enums if present.
  ([#699](python-attrs/cattrs#699))
- Aliases (when in use) now properly generate rename metadata in generated hooks.
  ([#706](python-attrs/cattrs#706) [#710](python-attrs/cattrs#710))
- _cattrs_ now tracks performance using [codspeed](https://codspeed.io/python-attrs/cattrs).
  ([#703](python-attrs/cattrs#703))
- The {mod}`tomlkit <cattrs.preconf.tomlkit>` preconf converter now properly handles native `date` objects when structuring.
  ([#707](python-attrs/cattrs#707) [#708](python-attrs/cattrs#708))
- The {mod}`tomlkit <cattrs.preconf.tomlkit>` preconf converter now passes date objects directly to _tomlkit_ for unstructuring.
  ([#707](python-attrs/cattrs#707) [#708](python-attrs/cattrs#708))
- Enum handling has been optimized by switching to hook factories, improving performance especially for plain enums.
  ([#705](python-attrs/cattrs#705))
- Fix {func}`cattrs.strategies.include_subclasses` when used with {func}`cattrs.strategies.configure_tagged_union` and classes using diamond inheritance.
  ([#685](python-attrs/cattrs#685) [#713](python-attrs/cattrs#713))
- Fix {func}`cattrs.strategies.configure_tagged_union` when used with recursive type aliases.
  ([#678](python-attrs/cattrs#678) [#714](python-attrs/cattrs#714))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants