Skip to content

Commit

Permalink
Support user defined generic field types in generic models. (#2554)
Browse files Browse the repository at this point in the history
Work on #2465 (see comments)

Co-authored-by: Samuel Colvin <s@muelcolvin.com>
  • Loading branch information
daviskirk and samuelcolvin committed May 11, 2021
1 parent 870cb6e commit 2bff0bf
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 2 deletions.
1 change: 1 addition & 0 deletions changes/2465-daviskirk.md
@@ -0,0 +1 @@
Support user defined generic field types in generic models.
7 changes: 6 additions & 1 deletion pydantic/generics.py
Expand Up @@ -172,7 +172,12 @@ def replace_types(type_: Any, type_map: Mapping[Any, Any]) -> Any:
# 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):
if (
origin_type is not None
and isinstance(type_, typing_base)
and not isinstance(origin_type, typing_base)
and getattr(type_, '_name', None) is not None
):
# 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
Expand Down
40 changes: 39 additions & 1 deletion tests/test_generics.py
@@ -1,6 +1,20 @@
import sys
from enum import Enum
from typing import Any, Callable, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union
from typing import (
Any,
Callable,
ClassVar,
Dict,
Generic,
List,
Mapping,
Optional,
Sequence,
Tuple,
Type,
TypeVar,
Union,
)

import pytest
from typing_extensions import Annotated, Literal
Expand Down Expand Up @@ -808,6 +822,30 @@ class Model(GenericModel, Generic[T]):
assert replace_types(list[Union[str, list, T]], {T: int}) == list[Union[str, list, int]]


@skip_36
def test_replace_types_with_user_defined_generic_type_field():
"""Test that using user defined generic types as generic model fields are handled correctly."""

T = TypeVar('T')
KT = TypeVar('KT')
VT = TypeVar('VT')

class GenericMapping(Mapping[KT, VT]):
pass

class GenericList(List[T]):
pass

class Model(GenericModel, Generic[T, KT, VT]):

map_field: GenericMapping[KT, VT]
list_field: GenericList[T]

assert replace_types(Model, {T: bool, KT: str, VT: int}) == Model[bool, str, int]
assert replace_types(Model[T, KT, VT], {T: bool, KT: str, VT: int}) == Model[bool, str, int]
assert replace_types(Model[T, VT, KT], {T: bool, KT: str, VT: int}) == Model[T, VT, KT][bool, int, str]


@skip_36
def test_replace_types_identity_on_unchanged():
T = TypeVar('T')
Expand Down

0 comments on commit 2bff0bf

Please sign in to comment.