Repository: pydantic/pydantic

Issue:
GenericModel does not support generic subclasses
# Bug

It seems the implementation of pydantic.generics.GenericModel does not support generic subclasses. Example:
```
import pydantic.generics
import typing

T = typing.TypeVar("T")

class ResponseModel(pydantic.generics.GenericModel, typing.Generic[T]):
    data: List[T]

class PaginatedResponseModel(ResponseModel[T]):
    total_count: int

ConcreteModel = PaginatedReponseModel[dict] # TypeError: Cannot parameterize a concrete instantiation of a generic model

```
`PaginatedReponseModel` is erroneously considered "concrete" even though type variables are used as parameters, and not concrete types.
It seems GenericModel.__class_getitem__ does not check if any parameters are `TypeVar`, like `typing.Generic` does. 

To support generic subclasses, `__class_getitem__` should check if all of the parameters are fully concrete type(i.e. any of them are `typing.TypeVar`s, or any of them are generic types that have non-concrete type parameters). If not, a concrete model should not be created.

I would consider this a bug since it does not follow the behavior of other `typing.Generic` usages, but it could also be a feature request.

Please complete:
* OS: Ubuntu 18.04.1
* Python version `import sys; print(sys.version)`: 3.7.1 (default, Oct 22 2018, 11:21:55) 
[GCC 8.2.0]
* Pydantic version `import pydantic; print(pydantic.VERSION)`: `1.0`

GenericModel - inheritance and Optional[TypeX] - fail to instantiate a subclass
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.4
            pydantic compiled: False
                 install path: /Users/thaisdeboisfosse/envs/thenv/lib/python3.7/site-packages/pydantic
               python version: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21)  [Clang 6.0 (clang-600.0.57)]
                     platform: Darwin-19.3.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```
### Summary

Thank you for the lib ! I found a weird bug while using GenericModel.

I create a BaseClass that inherits GenericModel and uses Generic. It contains a property X of type Optional[TypeX].

I create a subclass that fixes the Generic at runtime. The bug is that I cannot define the type of the Generic without an error.

If `X` is of type `TypeX` instead of `Optional[TypeX]` then there is no error (it is what I ended up doing).

### Code to reproduce the error

```py
import pydantic

TypeX = TypeVar('TypeX')

class BaseClass(GenericModel, Generic[TypeX]):
    X: Optional[TypeX]  # This causes the error

class ChildClass(BaseClass[TypeX], Generic[TypeX]):
    pass

ChildClass[int]
```

Error: 
```
cls = <class 'test_base_format.test_pydantic_failes.<locals>.ChildClass'>
params = <class 'int'>

    def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]:
        cached = _generic_types_cache.get((cls, params))
        if cached is not None:
            return cached
        if cls.__concrete__:
>           raise TypeError('Cannot parameterize a concrete instantiation of a generic model')
E           TypeError: Cannot parameterize a concrete instantiation of a generic model
```
GenericModel - inheritance and Optional[TypeX] - fail to instantiate a subclass
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.4
            pydantic compiled: False
                 install path: /Users/thaisdeboisfosse/envs/thenv/lib/python3.7/site-packages/pydantic
               python version: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21)  [Clang 6.0 (clang-600.0.57)]
                     platform: Darwin-19.3.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```
### Summary

Thank you for the lib ! I found a weird bug while using GenericModel.

I create a BaseClass that inherits GenericModel and uses Generic. It contains a property X of type Optional[TypeX].

I create a subclass that fixes the Generic at runtime. The bug is that I cannot define the type of the Generic without an error.

If `X` is of type `TypeX` instead of `Optional[TypeX]` then there is no error (it is what I ended up doing).

### Code to reproduce the error

```py
import pydantic

TypeX = TypeVar('TypeX')

class BaseClass(GenericModel, Generic[TypeX]):
    X: Optional[TypeX]  # This causes the error

class ChildClass(BaseClass[TypeX], Generic[TypeX]):
    pass

ChildClass[int]
```

Error: 
```
cls = <class 'test_base_format.test_pydantic_failes.<locals>.ChildClass'>
params = <class 'int'>

    def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]:
        cached = _generic_types_cache.get((cls, params))
        if cached is not None:
            return cached
        if cls.__concrete__:
>           raise TypeError('Cannot parameterize a concrete instantiation of a generic model')
E           TypeError: Cannot parameterize a concrete instantiation of a generic model
```


Comments:
Yep this is definitely a bug.

In general, I think it should be possible to subclass even concrete generic models. I'm hopeful it's not too hard to implement.
@dmontagu is the expert on `GenericModel`, any idea?
I'm hitting this as well, it looks like the value of "concrete" is calculated by running:

```
        concrete = all(not _is_typevar(v) for v in concrete_type_hints.values())
```

and Optional[T] does not qualify as a typevar even though it should.
The same goes for any more nested objects (including nested generic models).

Most of it can probably be fixed by modifying `_is_typevar` to detect nested type vars.
@dmontagu is the expert on `GenericModel`, any idea?
I'm hitting this as well, it looks like the value of "concrete" is calculated by running:

```
        concrete = all(not _is_typevar(v) for v in concrete_type_hints.values())
```

and Optional[T] does not qualify as a typevar even though it should.
The same goes for any more nested objects (including nested generic models).

Most of it can probably be fixed by modifying `_is_typevar` to detect nested type vars.

Patch:
diff --git a/pydantic/generics.py b/pydantic/generics.py
--- a/pydantic/generics.py
+++ b/pydantic/generics.py
@@ -1,10 +1,14 @@
 import sys
+import typing
 from typing import (
     TYPE_CHECKING,
     Any,
     ClassVar,
     Dict,
     Generic,
+    Iterable,
+    Iterator,
+    List,
     Optional,
     Tuple,
     Type,
@@ -17,8 +21,8 @@
 from .class_validators import gather_all_validators
 from .fields import FieldInfo, ModelField
 from .main import BaseModel, create_model
-from .typing import get_origin
-from .utils import lenient_issubclass
+from .typing import display_as_type, get_args, get_origin, typing_base
+from .utils import all_identical, lenient_issubclass
 
 _generic_types_cache: Dict[Tuple[Type[Any], Union[Any, Tuple[Any, ...]]], Type[BaseModel]] = {}
 GenericModelT = TypeVar('GenericModelT', bound='GenericModel')
@@ -37,6 +41,16 @@ class GenericModel(BaseModel):
 
     # Setting the return type as Type[Any] instead of Type[BaseModel] prevents PyCharm warnings
     def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[Type[Any], ...]]) -> Type[Any]:
+        """Instantiates a new class from a generic class `cls` and type variables `params`.
+
+        :param params: Tuple of types the class . Given a generic class
+            `Model` with 2 type variables and a concrete model `Model[str, int]`,
+            the value `(str, int)` would be passed to `params`.
+        :return: New model class inheriting from `cls` with instantiated
+            types described by `params`. If no parameters are given, `cls` is
+            returned as is.
+
+        """
         cached = _generic_types_cache.get((cls, params))
         if cached is not None:
             return cached
@@ -50,16 +64,24 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
             raise TypeError(f'Type {cls.__name__} must inherit from typing.Generic before being parameterized')
 
         check_parameters_count(cls, params)
+        # Build map from generic typevars to passed params
         typevars_map: Dict[TypeVarType, Type[Any]] = dict(zip(cls.__parameters__, params))
+        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.
         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: resolve_type_hint(v, typevars_map) for k, v in instance_type_hints.items()
+            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, typevars_map)
+        fields = _build_generic_fields(cls.__fields__, concrete_type_hints)
         model_module, called_globally = get_caller_frame_info()
         created_model = cast(
             Type[GenericModel],  # casting ensures mypy is aware of the __concrete__ and __parameters__ attributes
@@ -82,12 +104,20 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
                 reference_name += '_'
 
         created_model.Config = cls.Config
-        concrete = all(not _is_typevar(v) for v in concrete_type_hints.values())
-        created_model.__concrete__ = concrete
-        if not concrete:
-            parameters = tuple(v for v in concrete_type_hints.values() if _is_typevar(v))
-            parameters = tuple({k: None for k in parameters}.keys())  # get unique params while maintaining order
-            created_model.__parameters__ = parameters
+
+        # Find any typevars that are still present in the model.
+        # If none are left, the model is fully "concrete", otherwise the new
+        # class is a generic class as well taking the found typevars as
+        # parameters.
+        new_params = tuple(
+            {param: None for param in iter_contained_typevars(typevars_map.values())}
+        )  # use dict as ordered set
+        created_model.__concrete__ = not new_params
+        if new_params:
+            created_model.__parameters__ = new_params
+
+        # Save created model in cache so we don't end up creating duplicate
+        # models that should be identical.
         _generic_types_cache[(cls, params)] = created_model
         if len(params) == 1:
             _generic_types_cache[(cls, params[0])] = created_model
@@ -95,19 +125,74 @@ def __class_getitem__(cls: Type[GenericModelT], params: Union[Type[Any], Tuple[T
 
     @classmethod
     def __concrete_name__(cls: Type[Any], params: Tuple[Type[Any], ...]) -> str:
+        """Compute class name for child classes.
+
+        :param params: Tuple of types the class . Given a generic class
+            `Model` with 2 type variables and a concrete model `Model[str, int]`,
+            the value `(str, int)` would be passed to `params`.
+        :return: String representing a the new class where `params` are
+            passed to `cls` as type variables.
+
+        This method can be overridden to achieve a custom naming scheme for GenericModels.
         """
-        This method can be overridden to achieve a custom naming scheme for GenericModels
-        """
-        param_names = [param.__name__ if hasattr(param, '__name__') else str(param) for param in params]
+        param_names = [display_as_type(param) for param in params]
         params_component = ', '.join(param_names)
         return f'{cls.__name__}[{params_component}]'
 
 
-def resolve_type_hint(type_: Any, typevars_map: Dict[Any, Any]) -> Type[Any]:
-    if get_origin(type_) and getattr(type_, '__parameters__', None):
-        concrete_type_args = tuple([typevars_map[x] for x in type_.__parameters__])
-        return type_[concrete_type_args]
-    return typevars_map.get(type_, type_)
+def replace_types(type_: Any, type_map: Dict[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.
+    :return: New type representing the basic structure of `type_` with all
+        `typevar_map` keys recursively replaced.
+
+    >>> replace_types(Tuple[str, Union[List[str], float]], {str: int})
+    Tuple[int, Union[List[int], float]]
+
+    """
+    if not type_map:
+        return type_
+
+    type_args = get_args(type_)
+    origin_type = get_origin(type_)
+
+    # Having type args is a good indicator that this is a typing module
+    # class instantiation or a generic alias of some sort.
+    if type_args:
+        resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args)
+        if all_identical(type_args, resolved_type_args):
+            # If all arguments are the same, there is no need to modify the
+            # type or create a new object at all
+            return type_
+        if origin_type is not None and isinstance(type_, typing_base) and not isinstance(origin_type, typing_base):
+            # In python < 3.9 generic aliases don't exist so any of these like `list`,
+            # `type` or `collections.abc.Callable` need to be translated.
+            # See: https://www.python.org/dev/peps/pep-0585
+            origin_type = getattr(typing, type_._name)
+        return origin_type[resolved_type_args]
+
+    # We handle pydantic generic models separately as they don't have the same
+    # semantics as "typing" classes or generic aliases
+    if not origin_type and lenient_issubclass(type_, GenericModel) and not type_.__concrete__:
+        type_args = type_.__parameters__
+        resolved_type_args = tuple(replace_types(t, type_map) for t in type_args)
+        if all_identical(type_args, resolved_type_args):
+            return type_
+        return type_[resolved_type_args]
+
+    # Handle special case for typehints that can have lists as arguments.
+    # `typing.Callable[[int, str], int]` is an example for this.
+    if isinstance(type_, (List, list)):
+        resolved_list = list(replace_types(element, type_map) for element in type_)
+        if all_identical(type_, resolved_list):
+            return type_
+        return resolved_list
+
+    # If all else fails, we try to resolve the type directly and otherwise just
+    # return the input with no modifications.
+    return type_map.get(type_, type_)
 
 
 def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...]) -> None:
@@ -118,27 +203,26 @@ def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...])
         raise TypeError(f'Too {description} parameters for {cls.__name__}; actual {actual}, expected {expected}')
 
 
+def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]:
+    """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found."""
+    if isinstance(v, TypeVar):
+        yield v
+    elif hasattr(v, '__parameters__') and not get_origin(v) and lenient_issubclass(v, GenericModel):
+        yield from v.__parameters__
+    elif isinstance(v, Iterable):
+        for var in v:
+            yield from iter_contained_typevars(var)
+    else:
+        args = get_args(v)
+        for arg in args:
+            yield from iter_contained_typevars(arg)
+
+
 def _build_generic_fields(
     raw_fields: Dict[str, ModelField],
     concrete_type_hints: Dict[str, Type[Any]],
-    typevars_map: Dict[TypeVarType, Type[Any]],
 ) -> Dict[str, Tuple[Type[Any], FieldInfo]]:
-    return {
-        k: (_parameterize_generic_field(v, typevars_map), raw_fields[k].field_info)
-        for k, v in concrete_type_hints.items()
-        if k in raw_fields
-    }
-
-
-def _parameterize_generic_field(field_type: Type[Any], typevars_map: Dict[TypeVarType, Type[Any]]) -> Type[Any]:
-    if lenient_issubclass(field_type, GenericModel) and not field_type.__concrete__:
-        parameters = tuple(typevars_map.get(param, param) for param in field_type.__parameters__)
-        field_type = field_type[parameters]
-    return field_type
-
-
-def _is_typevar(v: Any) -> bool:
-    return isinstance(v, TypeVar)
+    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]:
diff --git a/pydantic/typing.py b/pydantic/typing.py
--- a/pydantic/typing.py
+++ b/pydantic/typing.py
@@ -25,6 +25,12 @@
 except ImportError:
     from typing import _Final as typing_base  # type: ignore
 
+try:
+    from typing import GenericAlias  # type: ignore
+except ImportError:
+    # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on)
+    GenericAlias = ()
+
 
 if sys.version_info < (3, 7):
     if TYPE_CHECKING:
@@ -74,7 +80,7 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
     AnyCallable = TypingCallable[..., Any]
     NoArgAnyCallable = TypingCallable[[], Any]
 
-if sys.version_info < (3, 8):
+if sys.version_info < (3, 8):  # noqa: C901
     if TYPE_CHECKING:
         from typing_extensions import Literal
     else:  # due to different mypy warnings raised during CI for python 3.7 and 3.8
@@ -83,8 +89,34 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
         except ImportError:
             Literal = None
 
-    def get_args(t: Type[Any]) -> Tuple[Any, ...]:
-        return getattr(t, '__args__', ())
+    if sys.version_info < (3, 7):
+
+        def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+            """Simplest get_args compatability layer possible.
+
+            The Python 3.6 typing module does not have `_GenericAlias` so
+            this won't work for everything. In particular this will not
+            support the `generics` module (we don't support generic models in
+            python 3.6).
+
+            """
+            return getattr(t, '__args__', ())
+
+    else:
+        from typing import _GenericAlias
+
+        def get_args(t: Type[Any]) -> Tuple[Any, ...]:
+            """Compatability version of get_args for python 3.7.
+
+            Mostly compatible with the python 3.8 `typing` module version
+            and able to handle almost all use cases.
+            """
+            if isinstance(t, _GenericAlias):
+                res = t.__args__
+                if t.__origin__ is Callable and res and res[0] is not Ellipsis:
+                    res = (list(res[:-1]), res[-1])
+                return res
+            return getattr(t, '__args__', ())
 
     def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
         return getattr(t, '__origin__', None)
@@ -179,7 +211,7 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
 
 
 def display_as_type(v: Type[Any]) -> str:
-    if not isinstance(v, typing_base) and not isinstance(v, type):
+    if not isinstance(v, typing_base) and not isinstance(v, GenericAlias) and not isinstance(v, type):
         v = v.__class__
 
     if isinstance(v, type) and issubclass(v, Enum):
@@ -190,6 +222,10 @@ def display_as_type(v: Type[Any]) -> str:
         else:
             return 'enum'
 
+    if isinstance(v, GenericAlias):
+        # Generic alias are constructs like `list[int]`
+        return str(v).replace('typing.', '')
+
     try:
         return v.__name__
     except AttributeError:
diff --git a/pydantic/utils.py b/pydantic/utils.py
--- a/pydantic/utils.py
+++ b/pydantic/utils.py
@@ -2,7 +2,7 @@
 import weakref
 from collections import OrderedDict, defaultdict, deque
 from copy import deepcopy
-from itertools import islice
+from itertools import islice, zip_longest
 from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType
 from typing import (
     TYPE_CHECKING,
@@ -11,6 +11,7 @@
     Callable,
     Dict,
     Generator,
+    Iterable,
     Iterator,
     List,
     Mapping,
@@ -638,3 +639,21 @@ def is_valid_private_name(name: str) -> bool:
         '__module__',
         '__qualname__',
     }
+
+
+_EMPTY = object()
+
+
+def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool:
+    """Check that the items of `left` are the same objects as those in `right`.
+
+    >>> a, b = object(), object()
+    >>> all_identical([a, b, a], [a, b, a])
+    True
+    >>> all_identical([a, b, [a]], [a, b, [a]])  # new list object, while "equal" is not "identical"
+    False
+    """
+    for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY):
+        if left_item is not right_item:
+            return False
+    return True


Test patch:
diff --git a/tests/test_generics.py b/tests/test_generics.py
--- a/tests/test_generics.py
+++ b/tests/test_generics.py
@@ -1,11 +1,11 @@
 import sys
 from enum import Enum
-from typing import Any, ClassVar, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
+from typing import Any, Callable, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union
 
 import pytest
 
 from pydantic import BaseModel, Field, ValidationError, root_validator, validator
-from pydantic.generics import GenericModel, _generic_types_cache
+from pydantic.generics import GenericModel, _generic_types_cache, iter_contained_typevars, replace_types
 
 skip_36 = pytest.mark.skipif(sys.version_info < (3, 7), reason='generics only supported for python 3.7 and above')
 
@@ -17,7 +17,10 @@ def test_generic_name():
     class Result(GenericModel, Generic[data_type]):
         data: data_type
 
-    assert Result[List[int]].__name__ == 'Result[typing.List[int]]'
+    if sys.version_info >= (3, 9):
+        assert Result[list[int]].__name__ == 'Result[list[int]]'
+    assert Result[List[int]].__name__ == 'Result[List[int]]'
+    assert Result[int].__name__ == 'Result[int]'
 
 
 @skip_36
@@ -248,35 +251,6 @@ class Config:
         result.data = 2
 
 
-@skip_36
-def test_deep_generic():
-    T = TypeVar('T')
-    S = TypeVar('S')
-    R = TypeVar('R')
-
-    class OuterModel(GenericModel, Generic[T, S, R]):
-        a: Dict[R, Optional[List[T]]]
-        b: Optional[Union[S, R]]
-        c: R
-        d: float
-
-    class InnerModel(GenericModel, Generic[T, R]):
-        c: T
-        d: R
-
-    class NormalModel(BaseModel):
-        e: int
-        f: str
-
-    inner_model = InnerModel[int, str]
-    generic_model = OuterModel[inner_model, NormalModel, int]
-
-    inner_models = [inner_model(c=1, d='a')]
-    generic_model(a={1: inner_models, 2: None}, b=None, c=1, d=1.5)
-    generic_model(a={}, b=NormalModel(e=1, f='a'), c=1, d=1.5)
-    generic_model(a={}, b=1, c=1, d=1.5)
-
-
 @skip_36
 def test_enum_generic():
     T = TypeVar('T')
@@ -498,6 +472,26 @@ class Model(GenericModel, Generic[AT, BT]):
     ]
 
 
+@skip_36
+def test_partial_specification_with_inner_typevar():
+    AT = TypeVar('AT')
+    BT = TypeVar('BT')
+
+    class Model(GenericModel, Generic[AT, BT]):
+        a: List[AT]
+        b: List[BT]
+
+    partial_model = Model[str, BT]
+    assert partial_model.__concrete__ is False
+    concrete_model = partial_model[int]
+    assert concrete_model.__concrete__ is True
+
+    # nested resolution of partial models should work as expected
+    nested_resolved = concrete_model(a=[123], b=['456'])
+    assert nested_resolved.a == ['123']
+    assert nested_resolved.b == [456]
+
+
 @skip_36
 def test_partial_specification_name():
     AT = TypeVar('AT')
@@ -681,7 +675,11 @@ def get_generic(t):
 
 
 @skip_36
-def test_generic_model_redefined_without_cache_fail(create_module):
+def test_generic_model_redefined_without_cache_fail(create_module, monkeypatch):
+
+    # match identity checker otherwise we never get to the redefinition check
+    monkeypatch.setattr('pydantic.generics.all_identical', lambda left, right: False)
+
     @create_module
     def module():
         from typing import Generic, TypeVar
@@ -756,3 +754,264 @@ def test_get_caller_frame_info_when_sys_getframe_undefined():
         assert get_caller_frame_info() == (None, False)
     finally:  # just to make sure we always setting original attribute back
         sys._getframe = getframe
+
+
+@skip_36
+def test_iter_contained_typevars():
+    T = TypeVar('T')
+    T2 = TypeVar('T2')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert list(iter_contained_typevars(Model[T])) == [T]
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[T]]]])) == [T]
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[int]]]])) == []
+    assert list(iter_contained_typevars(Optional[List[Union[str, Model[T], Callable[[T2, T], str]]]])) == [T, T2, T]
+
+
+@skip_36
+def test_nested_identity_parameterization():
+    T = TypeVar('T')
+    T2 = TypeVar('T2')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert Model[T][T][T] is Model
+    assert Model[T] is Model
+    assert Model[T2] is not Model
+
+
+@skip_36
+def test_replace_types():
+    T = TypeVar('T')
+
+    class Model(GenericModel, Generic[T]):
+        a: T
+
+    assert replace_types(T, {T: int}) is int
+    assert replace_types(List[Union[str, list, T]], {T: int}) == List[Union[str, list, int]]
+    assert replace_types(Callable, {T: int}) == Callable
+    assert replace_types(Callable[[int, str, T], T], {T: int}) == Callable[[int, str, int], int]
+    assert replace_types(T, {}) is T
+    assert replace_types(Model[List[T]], {T: int}) == Model[List[T]][int]
+    assert replace_types(T, {}) is T
+    assert replace_types(Type[T], {T: int}) == Type[int]
+    assert replace_types(Model[T], {T: T}) == Model[T]
+
+    if sys.version_info >= (3, 9):
+        # Check generic aliases (subscripted builtin types) to make sure they
+        # resolve correctly (don't get translated to typing versions for
+        # example)
+        assert replace_types(list[Union[str, list, T]], {T: int}) == list[Union[str, list, int]]
+
+
+@skip_36
+def test_replace_types_identity_on_unchanged():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    type_ = List[Union[str, Callable[[list], Optional[str]], U]]
+    assert replace_types(type_, {T: int}) is type_
+
+
+@skip_36
+def test_deep_generic():
+    T = TypeVar('T')
+    S = TypeVar('S')
+    R = TypeVar('R')
+
+    class OuterModel(GenericModel, Generic[T, S, R]):
+        a: Dict[R, Optional[List[T]]]
+        b: Optional[Union[S, R]]
+        c: R
+        d: float
+
+    class InnerModel(GenericModel, Generic[T, R]):
+        c: T
+        d: R
+
+    class NormalModel(BaseModel):
+        e: int
+        f: str
+
+    inner_model = InnerModel[int, str]
+    generic_model = OuterModel[inner_model, NormalModel, int]
+
+    inner_models = [inner_model(c=1, d='a')]
+    generic_model(a={1: inner_models, 2: None}, b=None, c=1, d=1.5)
+    generic_model(a={}, b=NormalModel(e=1, f='a'), c=1, d=1.5)
+    generic_model(a={}, b=1, c=1, d=1.5)
+
+    assert InnerModel.__concrete__ is False
+    assert inner_model.__concrete__ is True
+
+
+@skip_36
+def test_deep_generic_with_inner_typevar():
+    T = TypeVar('T')
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: List[T]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a=['wrong'])
+    assert InnerModel[int](a=['1']).a == [1]
+
+
+@skip_36
+def test_deep_generic_with_referenced_generic():
+    T = TypeVar('T')
+    R = TypeVar('R')
+
+    class ReferencedModel(GenericModel, Generic[R]):
+        a: R
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: ReferencedModel[T]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a={'a': 'wrong'})
+    assert InnerModel[int](a={'a': 1}).a.a == 1
+
+
+@skip_36
+def test_deep_generic_with_referenced_inner_generic():
+    T = TypeVar('T')
+
+    class ReferencedModel(GenericModel, Generic[T]):
+        a: T
+
+    class OuterModel(GenericModel, Generic[T]):
+        a: Optional[List[Union[ReferencedModel[T], str]]]
+
+    class InnerModel(OuterModel[T], Generic[T]):
+        pass
+
+    assert InnerModel[int].__concrete__ is True
+    assert InnerModel.__concrete__ is False
+
+    with pytest.raises(ValidationError):
+        InnerModel[int](a=['s', {'a': 'wrong'}])
+    assert InnerModel[int](a=['s', {'a': 1}]).a[1].a == 1
+
+    assert InnerModel[int].__fields__['a'].outer_type_ == List[Union[ReferencedModel[int], str]]
+    assert (InnerModel[int].__fields__['a'].sub_fields[0].sub_fields[0].outer_type_.__fields__['a'].outer_type_) == int
+
+
+@skip_36
+def test_deep_generic_with_multiple_typevars():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    class OuterModel(GenericModel, Generic[T]):
+        data: List[T]
+
+    class InnerModel(OuterModel[T], Generic[U, T]):
+        extra: U
+
+    ConcreteInnerModel = InnerModel[int, float]
+    assert ConcreteInnerModel.__fields__['data'].outer_type_ == List[float]
+    assert ConcreteInnerModel.__fields__['extra'].outer_type_ == int
+
+    assert ConcreteInnerModel(data=['1'], extra='2').dict() == {'data': [1.0], 'extra': 2}
+
+
+@skip_36
+def test_deep_generic_with_multiple_inheritance():
+    K = TypeVar('K')
+    V = TypeVar('V')
+    T = TypeVar('T')
+
+    class OuterModelA(GenericModel, Generic[K, V]):
+        data: Dict[K, V]
+
+    class OuterModelB(GenericModel, Generic[T]):
+        stuff: List[T]
+
+    class InnerModel(OuterModelA[K, V], OuterModelB[T], Generic[K, V, T]):
+        extra: int
+
+    ConcreteInnerModel = InnerModel[int, float, str]
+
+    assert ConcreteInnerModel.__fields__['data'].outer_type_ == Dict[int, float]
+    assert ConcreteInnerModel.__fields__['stuff'].outer_type_ == List[str]
+    assert ConcreteInnerModel.__fields__['extra'].outer_type_ == int
+
+    ConcreteInnerModel(data={1.1: '5'}, stuff=[123], extra=5).dict() == {
+        'data': {1: 5},
+        'stuff': ['123'],
+        'extra': 5,
+    }
+
+
+@skip_36
+def test_generic_with_referenced_generic_type_1():
+    T = TypeVar('T')
+
+    class ModelWithType(GenericModel, Generic[T]):
+        # Type resolves to type origin of "type" which is non-subscriptible for
+        # python < 3.9 so we want to make sure it works for other versions
+        some_type: Type[T]
+
+    class ReferenceModel(GenericModel, Generic[T]):
+        abstract_base_with_type: ModelWithType[T]
+
+    ReferenceModel[int]
+
+
+@skip_36
+def test_generic_with_referenced_nested_typevar():
+    T = TypeVar('T')
+
+    class ModelWithType(GenericModel, Generic[T]):
+        # Type resolves to type origin of "collections.abc.Sequence" which is
+        # non-subscriptible for
+        # python < 3.9 so we want to make sure it works for other versions
+        some_type: Sequence[T]
+
+    class ReferenceModel(GenericModel, Generic[T]):
+        abstract_base_with_type: ModelWithType[T]
+
+    ReferenceModel[int]
+
+
+@skip_36
+def test_generic_with_callable():
+    T = TypeVar('T')
+
+    class Model(GenericModel, Generic[T]):
+        # Callable is a test for any type that accepts a list as an argument
+        some_callable: Callable[[Optional[int], T], None]
+
+    Model[str].__concrete__ is True
+    Model.__concrete__ is False
+
+
+@skip_36
+def test_generic_with_partial_callable():
+    T = TypeVar('T')
+    U = TypeVar('U')
+
+    class Model(GenericModel, Generic[T, U]):
+        t: T
+        u: U
+        # Callable is a test for any type that accepts a list as an argument
+        some_callable: Callable[[Optional[int], str], None]
+
+    Model[str, U].__concrete__ is False
+    Model[str, U].__parameters__ == [U]
+    Model[str, int].__concrete__ is False
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -27,6 +27,7 @@
     BUILTIN_COLLECTIONS,
     ClassAttribute,
     ValueItems,
+    all_identical,
     deep_update,
     get_model,
     import_string,
@@ -60,11 +61,18 @@ def test_import_no_attr():
     assert exc_info.value.args[0] == 'Module "os" does not define a "foobar" attribute'
 
 
-@pytest.mark.parametrize('value,expected', ((str, 'str'), ('string', 'str'), (Union[str, int], 'Union[str, int]')))
+@pytest.mark.parametrize(
+    'value,expected', ((str, 'str'), ('string', 'str'), (Union[str, int], 'Union[str, int]'), (list, 'list'))
+)
 def test_display_as_type(value, expected):
     assert display_as_type(value) == expected
 
 
+@pytest.mark.skipif(sys.version_info < (3, 9), reason='generic aliases are not available in python < 3.9')
+def test_display_as_type_generic_alias():
+    assert display_as_type(list[[Union[str, int]]]) == 'list[[Union[str, int]]]'
+
+
 def test_display_as_type_enum():
     class SubField(Enum):
         a = 1
@@ -446,3 +454,18 @@ def test_resolve_annotations_no_module():
     # TODO: is there a better test for this, can this case really happen?
     fr = ForwardRef('Foo')
     assert resolve_annotations({'Foo': ForwardRef('Foo')}, None) == {'Foo': fr}
+
+
+def test_all_identical():
+    a, b = object(), object()
+    c = [b]
+    assert all_identical([a, b], [a, b]) is True
+    assert all_identical([a, b], [a, b]) is True
+    assert all_identical([a, b, b], [a, b, b]) is True
+    assert all_identical([a, c, b], [a, c, b]) is True
+
+    assert all_identical([], [a]) is False, 'Expected iterables with different lengths to evaluate to `False`'
+    assert all_identical([a], []) is False, 'Expected iterables with different lengths to evaluate to `False`'
+    assert (
+        all_identical([a, [b], b], [a, [b], b]) is False
+    ), 'New list objects are different objects and should therefor not be identical.'