mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
fix: avoid RecursionError when using some types like Enum or Literal with generic models (#2438)
* fix: support properly `Enum` when combined with generic models * whitelist iterables * update change description * add test for Literal Co-authored-by: Samuel Colvin <s@muelcolvin.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Avoid `RecursionError` when using some types like `Enum` or `Literal` with generic models
|
||||
@@ -6,7 +6,6 @@ from typing import (
|
||||
ClassVar,
|
||||
Dict,
|
||||
Generic,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
@@ -205,13 +204,16 @@ def check_parameters_count(cls: Type[GenericModel], parameters: Tuple[Any, ...])
|
||||
raise TypeError(f'Too {description} parameters for {cls.__name__}; actual {actual}, expected {expected}')
|
||||
|
||||
|
||||
DictValues: Type[Any] = {}.values().__class__
|
||||
|
||||
|
||||
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):
|
||||
elif isinstance(v, (DictValues, list)):
|
||||
for var in v:
|
||||
yield from iter_contained_typevars(var)
|
||||
else:
|
||||
|
||||
@@ -3,6 +3,7 @@ from enum import Enum
|
||||
from typing import Any, Callable, ClassVar, Dict, Generic, List, Optional, Sequence, Tuple, Type, TypeVar, Union
|
||||
|
||||
import pytest
|
||||
from typing_extensions import Literal
|
||||
|
||||
from pydantic import BaseModel, Field, ValidationError, root_validator, validator
|
||||
from pydantic.generics import GenericModel, _generic_types_cache, iter_contained_typevars, replace_types
|
||||
@@ -1039,3 +1040,34 @@ def test_generic_recursive_models(create_module):
|
||||
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'))))
|
||||
|
||||
|
||||
@skip_36
|
||||
def test_generic_enum():
|
||||
T = TypeVar('T')
|
||||
|
||||
class SomeGenericModel(GenericModel, Generic[T]):
|
||||
some_field: T
|
||||
|
||||
class SomeStringEnum(str, Enum):
|
||||
A = 'A'
|
||||
B = 'B'
|
||||
|
||||
class MyModel(BaseModel):
|
||||
my_gen: SomeGenericModel[SomeStringEnum]
|
||||
|
||||
m = MyModel.parse_obj({'my_gen': {'some_field': 'A'}})
|
||||
assert m.my_gen.some_field is SomeStringEnum.A
|
||||
|
||||
|
||||
@skip_36
|
||||
def test_generic_literal():
|
||||
FieldType = TypeVar('FieldType')
|
||||
ValueType = TypeVar('ValueType')
|
||||
|
||||
class GModel(GenericModel, Generic[FieldType, ValueType]):
|
||||
field: Dict[FieldType, ValueType]
|
||||
|
||||
Fields = Literal['foo', 'bar']
|
||||
m = GModel[Fields, str](field={'foo': 'x'})
|
||||
assert m.dict() == {'field': {'foo': 'x'}}
|
||||
|
||||
Reference in New Issue
Block a user