Skip to content

Commit ab69114

Browse files
fix: avoid RecursionError when using some types like Enum or Literal with generic models (pydantic#2438)
* fix: support properly `Enum` when combined with generic models * whitelist iterables * update change description * add test for Literal Co-authored-by: Samuel Colvin <[email protected]>
1 parent 429b439 commit ab69114

File tree

3 files changed

+37
-2
lines changed

3 files changed

+37
-2
lines changed

changes/2436-PrettyWood.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid `RecursionError` when using some types like `Enum` or `Literal` with generic models

pydantic/generics.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
ClassVar,
77
Dict,
88
Generic,
9-
Iterable,
109
Iterator,
1110
List,
1211
Mapping,
@@ -205,13 +204,16 @@ def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...])
205204
raise TypeError(f'Too {description} parameters for {cls.__name__}; actual {actual}, expected {expected}')
206205

207206

207+
DictValues: Type[Any] = {}.values().__class__
208+
209+
208210
def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]:
209211
"""Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found."""
210212
if isinstance(v, TypeVar):
211213
yield v
212214
elif hasattr(v, '__parameters__') and not get_origin(v) and lenient_issubclass(v, GenericModel):
213215
yield from v.__parameters__
214-
elif isinstance(v, Iterable):
216+
elif isinstance(v, (DictValues, list)):
215217
for var in v:
216218
yield from iter_contained_typevars(var)
217219
else:

tests/test_generics.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, Callable, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union
44

55
import pytest
6+
from typing_extensions import Literal
67

78
from pydantic import BaseModel, Field, ValidationError, root_validator, validator
89
from pydantic.generics import GenericModel, _generic_types_cache, iter_contained_typevars, replace_types
@@ -1039,3 +1040,34 @@ class Model2(GenericModel, Generic[T]):
10391040
Model2 = module.Model2
10401041
result = Model1[str].parse_obj(dict(ref=dict(ref=dict(ref=dict(ref=123)))))
10411042
assert result == Model1(ref=Model2(ref=Model1(ref=Model2(ref='123'))))
1043+
1044+
1045+
@skip_36
1046+
def test_generic_enum():
1047+
T = TypeVar('T')
1048+
1049+
class SomeGenericModel(GenericModel, Generic[T]):
1050+
some_field: T
1051+
1052+
class SomeStringEnum(str, Enum):
1053+
A = 'A'
1054+
B = 'B'
1055+
1056+
class MyModel(BaseModel):
1057+
my_gen: SomeGenericModel[SomeStringEnum]
1058+
1059+
m = MyModel.parse_obj({'my_gen': {'some_field': 'A'}})
1060+
assert m.my_gen.some_field is SomeStringEnum.A
1061+
1062+
1063+
@skip_36
1064+
def test_generic_literal():
1065+
FieldType = TypeVar('FieldType')
1066+
ValueType = TypeVar('ValueType')
1067+
1068+
class GModel(GenericModel, Generic[FieldType, ValueType]):
1069+
field: Dict[FieldType, ValueType]
1070+
1071+
Fields = Literal['foo', 'bar']
1072+
m = GModel[Fields, str](field={'foo': 'x'})
1073+
assert m.dict() == {'field': {'foo': 'x'}}

0 commit comments

Comments
 (0)