pydantic/pydantic · error · RecursionError

{e.args[0]} If you made use of an implicit recursive type al

Error message

{e.args[0]}
If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), consider using PEP 695 type aliases instead. For more details, refer to the documentation: https://pydantic.dev/docs/validation/{version_short()}/concepts/types/#named-recursive-types

What it means

A `RecursionError` (with a note pointing to PEP 695) re-raised from `eval_type` when forward-reference evaluation blows the recursion limit. The classic cause is an implicit recursive type alias such as `MyType = list['MyType']`, where resolving the alias references itself indefinitely under the legacy alias semantics.

Source

Thrown at pydantic/_internal/_typing_extra.py:392

            message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.'
        else:
            message = f'Unable to evaluate type annotation {value!r}.'
        if sys.version_info >= (3, 11):
            e.add_note(message)
            raise
        else:
            raise TypeError(message) from e
    except RecursionError as e:
        message = (
            "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), "
            'consider using PEP 695 type aliases instead. For more details, refer to the documentation: '
            f'https://pydantic.dev/docs/validation/{version_short()}/concepts/types/#named-recursive-types'
        )
        if sys.version_info >= (3, 11):
            e.add_note(message)
            raise
        else:
            raise RecursionError(f'{e.args[0]}\n{message}')


@deprecated(
    '`eval_type_lenient()` is deprecated, use `try_eval_type()` instead.',
    category=None,
)
def eval_type_lenient(
    value: Any,
    globalns: GlobalsNamespace | None = None,
    localns: MappingNamespace | None = None,
) -> Any:  # pragma: no cover
    ev, _ = try_eval_type(value, globalns, localns)
    return ev


def _eval_type(
    value: Any,
    globalns: GlobalsNamespace | None = None,

View on GitHub (pinned to 2e5f0e2b42)

Solutions

  1. Switch to a PEP 695 type alias: `type MyType = list['MyType']` (lazy, recursion-safe).
  2. Alternatively model the recursion with a self-referencing BaseModel: `class Node(BaseModel): children: list['Node']`.
  3. If you must use the legacy form, make the alias refer to a forward-ref class name rather than to itself.

Example fix

# before
MyType = list['MyType']   # implicit recursive alias

class M(BaseModel):
    x: MyType   # RecursionError

# after (PEP 695, Python 3.12+)
type MyType = list['MyType']
class M(BaseModel):
    x: MyType
Defensive patterns

Strategy: validation

Type guard

import sys
def safe_alias_eval(alias_str, ns):
    sys.setrecursionlimit(200)
    try:
        eval(alias_str, ns)
        return True
    except RecursionError:
        return False

Prevention

When it happens

Trigger: Defining `MyType = list['MyType']` (pre-PEP-695 style) and using it on a Pydantic field. Each evaluation of the alias re-substitutes itself, recursing until the interpreter's limit is hit.

Common situations: Building tree/graph/JSON-like recursive structures; porting recursive type aliases between Python versions; using older tutorials that predate PEP 695.

Related errors


AI-assisted analysis of pydantic/pydantic@2e5f0e2b42 (2026-08-04). Data as JSON: /data/errors/2923991ea67cf2b4.json. Report an issue: GitHub.