Repository: pydantic/pydantic

Issue:
Recursive model: maximum recursion depth exceeded
# Bug

`RecursionError: maximum recursion depth exceeded while calling a Python object`

I get this error message, when using recursive models.

```py
class A(BaseModel):
    ...

class B(A):
    key: List[B]
    ...
```

Using

```py
from __future__ import annotations
from typings import ForwardRef

B = ForwardRef('B')
...
B.update_forward_refs()
```

does not make a difference aswell.

Issue #531 did not have any clue on how to solve this. The docs don't cover the usage with lists. 

Hope you understand my issue here :smile:

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.4
            pydantic compiled: True
                 install path: /usr/local/lib/python3.8/site-packages/pydantic
               python version: 3.8.2 (default, Feb 26 2020, 14:58:38)  [GCC 8.3.0]
                     platform: Linux-5.3.0-45-generic-x86_64-with-glibc2.2.5
     optional deps. installed: ['email-validator']
```

EDIT:
Just saw that the issue is pointed to in #524, but there is no response. Maybe there is a solution for it now?



Comments:
I don't think there's currently a solution for this. The most relevant discussion to this is #659, though I wouldn't all this a duplicate since that's specific to `from_orm`.

The short answer is that pydantic doesn't (yet) have any solution for detecting and coping with circular references.

If you have a suggestion of a solution, please comment on #659 or submit a demo PR. I have no idea off the top of my head, how hard it would be to use the same approach for `from_orm`, `parse_obj` and our case above - or if they're even particularly linked.

Patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -420,7 +420,7 @@ def prepare(self) -> None:
         e.g. calling it it multiple times may modify the field and configure it incorrectly.
         """
         self._set_default_and_type()
-        if self.type_.__class__ == ForwardRef:
+        if self.type_.__class__ is ForwardRef or self.type_.__class__ is DeferredType:
             # self.type_ is currently a ForwardRef and there's nothing we can do now,
             # user will need to call model.update_forward_refs()
             return
@@ -661,6 +661,8 @@ def validate(
         self, v: Any, values: Dict[str, Any], *, loc: 'LocStr', cls: Optional['ModelOrDc'] = None
     ) -> 'ValidateReturn':
 
+        assert self.type_.__class__ is not DeferredType
+
         if self.type_.__class__ is ForwardRef:
             assert cls is not None
             raise ConfigError(
@@ -946,3 +948,9 @@ def PrivateAttr(
         default,
         default_factory=default_factory,
     )
+
+
+class DeferredType:
+    """
+    Used to postpone field preparation, while creating recursive generic models.
+    """
diff --git a/pydantic/generics.py b/pydantic/generics.py
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -9,6 +9,7 @@
     Iterable,
     Iterator,
     List,
+    Mapping,
     Optional,
     Tuple,
     Type,
@@ -19,7 +20,7 @@
 )
 
 from .class_validators import gather_all_validators
-from .fields import FieldInfo, ModelField
+from .fields import DeferredType
 from .main import BaseModel, create_model
 from .typing import display_as_type, get_args, get_origin, typing_base
 from .utils import all_identical, lenient_issubclass
@@ -69,19 +70,15 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
         if all_identical(typevars_map.keys(), typevars_map.values()) and typevars_map:
             return cls  # if arguments are equal to parameters it's the same object
 
-        # Recursively walk class type hints and replace generic typevars
-        # with concrete types that were passed.
+        # Create new model with original model as parent inserting fields with DeferredType.
+        model_name = cls.__concrete_name__(params)
+        validators = gather_all_validators(cls)
+
         type_hints = get_type_hints(cls).items()
         instance_type_hints = {k: v for k, v in type_hints if get_origin(v) is not ClassVar}
-        concrete_type_hints: Dict[str, Type[Any]] = {
-            k: replace_types(v, typevars_map) for k, v in instance_type_hints.items()
-        }
 
-        # Create new model with original model as parent inserting fields with
-        # updated type hints.
-        model_name = cls.__concrete_name__(params)
-        validators = gather_all_validators(cls)
-        fields = _build_generic_fields(cls.__fields__, concrete_type_hints)
+        fields = {k: (DeferredType(), cls.__fields__[k].field_info) for k in instance_type_hints if k in cls.__fields__}
+
         model_module, called_globally = get_caller_frame_info()
         created_model = cast(
             Type[GenericModel],  # casting ensures mypy is aware of the __concrete__ and __parameters__ attributes
@@ -121,6 +118,11 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
         _generic_types_cache[(cls, params)] = created_model
         if len(params) == 1:
             _generic_types_cache[(cls, params[0])] = created_model
+
+        # Recursively walk class type hints and replace generic typevars
+        # with concrete types that were passed.
+        _prepare_model_fields(created_model, fields, instance_type_hints, typevars_map)
+
         return created_model
 
     @classmethod
@@ -140,11 +142,11 @@ def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
         return f'{cls.__name__}[{params_component}]'
 
 
-def replace_types(type_: Any, type_map: Dict[Any, Any]) -> Any:
+def replace_types(type_: Any, type_map: Mapping[Any, Any]) -> Any:
     """Return type with all occurances of `type_map` keys recursively replaced with their values.
 
     :param type_: Any type, class or generic alias
-    :type_map: Mapping from `TypeVar` instance to concrete types.
+    :param type_map: Mapping from `TypeVar` instance to concrete types.
     :return: New type representing the basic structure of `type_` with all
         `typevar_map` keys recursively replaced.
 
@@ -218,13 +220,6 @@ def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]:
             yield from iter_contained_typevars(arg)
 
 
-def _build_generic_fields(
-    raw_fields: Dict[str, ModelField],
-    concrete_type_hints: Dict[str, Type[Any]],
-) -> Dict[str, Tuple[Type[Any], FieldInfo]]:
-    return {k: (v, raw_fields[k].field_info) for k, v in concrete_type_hints.items() if k in raw_fields}
-
-
 def get_caller_frame_info() -> Tuple[Optional[str], bool]:
     """
     Used inside a function to check whether it was called globally
@@ -241,3 +236,29 @@ def get_caller_frame_info() -> Tuple[Optional[str], bool]:
         return None, False
     frame_globals = previous_caller_frame.f_globals
     return frame_globals.get('__name__'), previous_caller_frame.f_locals is frame_globals
+
+
+def _prepare_model_fields(
+    created_model: Type[GenericModel],
+    fields: Mapping[str, Any],
+    instance_type_hints: Mapping[str, type],
+    typevars_map: Mapping[Any, type],
+) -> None:
+    """
+    Replace DeferredType fields with concrete type hints and prepare them.
+    """
+
+    for key, field in created_model.__fields__.items():
+        if key not in fields:
+            assert field.type_.__class__ is not DeferredType
+            # https://github.com/nedbat/coveragepy/issues/198
+            continue  # pragma: no cover
+
+        assert field.type_.__class__ is DeferredType, field.type_.__class__
+
+        field_type_hint = instance_type_hints[key]
+        concrete_type = replace_types(field_type_hint, typevars_map)
+        field.type_ = concrete_type
+        field.outer_type_ = concrete_type
+        field.prepare()
+        created_model.__annotations__[key] = concrete_type


Test patch:
diff --git a/tests/test_generics.py b/tests/test_generics.py
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -1015,3 +1015,27 @@ class Model(GenericModel, Generic[T, U]):
     Model[str, U].__concrete__ is False
     Model[str, U].__parameters__ == [U]
     Model[str, int].__concrete__ is False
+
+
+@skip_36
+def test_generic_recursive_models(create_module):
+    @create_module
+    def module():
+        from typing import Generic, TypeVar, Union
+
+        from pydantic.generics import GenericModel
+
+        T = TypeVar('T')
+
+        class Model1(GenericModel, Generic[T]):
+            ref: 'Model2[T]'
+
+        class Model2(GenericModel, Generic[T]):
+            ref: Union[T, Model1[T]]
+
+        Model1.update_forward_refs()
+
+    Model1 = module.Model1
+    Model2 = module.Model2
+    result = Model1[str].parse_obj(dict(ref=dict(ref=dict(ref=dict(ref=123)))))
+    assert result == Model1(ref=Model2(ref=Model1(ref=Model2(ref='123'))))