Skip to content

Unquoted forward references in 3.14 don't work #740

@0e4ef622

Description

@0e4ef622

Minimal reproduction:

# /// script
# requires-python = ">=3.14"
# dependencies = [
#     "attrs>=26.1.0",
#     "cattrs>=26.1.0",
# ]
# ///

from attrs import define
from cattrs import structure, unstructure


@define
class A:
    a: A | None


v = A(None)
print(v)
print(unstructure(v))
print(structure(unstructure(v), A))
Output:
A(a=None)
{'a': None}
Traceback (most recent call last):
  File "/tmp/asdf/a.py", line 21, in <module>
    print(structure(unstructure(v), A))
          ~~~~~~~~~^^^^^^^^^^^^^^^^^^^
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/converters.py", line 591, in structure
    return self._structure_func.dispatch(cl)(obj, cl)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/dispatch.py", line 133, in dispatch_without_caching
    res = self._function_dispatch.dispatch(typ)
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/dispatch.py", line 76, in dispatch
    return handler(typ)
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/converters.py", line 1314, in gen_structure_attrs_fromdict
    return make_dict_structure_fn(
        cl,
    ...<5 lines>...
        **attrib_overrides,
    )
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/gen/__init__.py", line 831, in make_dict_structure_fn
    return make_dict_structure_fn_from_attrs(
        attrs,
    ...<9 lines>...
        **kwargs,
    )
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/gen/__init__.py", line 450, in make_dict_structure_fn_from_attrs
    handler = find_structure_handler(
        a, t, converter, _cattrs_prefer_attrib_converters
    )
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/gen/_shared.py", line 74, in find_structure_handler
    handler = c.get_structure_hook(type, cache_result=False)
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/converters.py", line 609, in get_structure_hook
    else self._structure_func.dispatch_without_caching(type)
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/dispatch.py", line 134, in dispatch_without_caching
    return res if res is not None else self._fallback_factory(typ)
                                       ~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/converters.py", line 1062, in <lambda>
    structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
                                                                       ~~~~~~~~~~~^
        None, t
        ^^^^^^^
    ),
    ^
  File "/home/_/.cache/uv/environments-v2/a-9e6cd7d0ec75859d/lib/python3.14/site-packages/cattrs/fns.py", line 22, in raise_error
    raise StructureHandlerNotFoundError(msg, type_=cl)
cattrs.errors.StructureHandlerNotFoundError: Unsupported type: ForwardRef('A | None', is_class=True, owner=<class '__main__.A'>). Register a structure hook for it.

One workaround is to put quotes around A | None.

Interestingly, the unquoted version does work in 3.13 with from __future__ import annotations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions