mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Support user defined generic field types in generic models. (#2554)
Work on #2465 (see comments) Co-authored-by: Samuel Colvin <s@muelcolvin.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Support user defined generic field types in generic models.
|
||||
@@ -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
|
||||
|
||||
+57
-1
@@ -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
|
||||
@@ -808,6 +822,30 @@ def test_replace_types():
|
||||
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')
|
||||
@@ -1073,6 +1111,24 @@ def test_generic_literal():
|
||||
assert m.dict() == {'field': {'foo': 'x'}}
|
||||
|
||||
|
||||
@skip_36
|
||||
def test_generic_with_user_defined_generic_field():
|
||||
T = TypeVar('T')
|
||||
|
||||
class GenericList(List[T]):
|
||||
pass
|
||||
|
||||
class Model(GenericModel, Generic[T]):
|
||||
|
||||
field: GenericList[T]
|
||||
|
||||
model = Model[int](field=[5])
|
||||
assert model.field[0] == 5
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
model = Model[int](field=['a'])
|
||||
|
||||
|
||||
@skip_36
|
||||
def test_generic_annotated():
|
||||
T = TypeVar('T')
|
||||
|
||||
Reference in New Issue
Block a user