I want to create a method on a base Pydantic model to instantiate child models with dummy data.
from __future__ import annotations
from pydantic import BaseModel
class BaseModelWrapper(BaseModel):
@classmethod
def make_dummy(cls) -> BaseModelWrapper:
for name, field in cls.model_fields.items():
if not field.is_required():
continue
# How can I create values based on the field type?
print(field.annotation)
return cls()
class XXX(BaseModelWrapper):
a: int | None
b: str
c: int
d: int | None = None
e: list[str]
# These should be equivalent
XXX.make_dummy()
XXX(a=None, b="", c=0, e=[])
The part I'm struggling with is how to programmatically map type annotations to values.
Let's say field.annotation
is int | None
. I could just create a dictionary to map that to None
, but there are tons of possible combinations of types, so this doesn't scale. There must be a cleaner way to create a value for each field.
I want to create a method on a base Pydantic model to instantiate child models with dummy data.
from __future__ import annotations
from pydantic import BaseModel
class BaseModelWrapper(BaseModel):
@classmethod
def make_dummy(cls) -> BaseModelWrapper:
for name, field in cls.model_fields.items():
if not field.is_required():
continue
# How can I create values based on the field type?
print(field.annotation)
return cls()
class XXX(BaseModelWrapper):
a: int | None
b: str
c: int
d: int | None = None
e: list[str]
# These should be equivalent
XXX.make_dummy()
XXX(a=None, b="", c=0, e=[])
The part I'm struggling with is how to programmatically map type annotations to values.
Let's say field.annotation
is int | None
. I could just create a dictionary to map that to None
, but there are tons of possible combinations of types, so this doesn't scale. There must be a cleaner way to create a value for each field.
- If you a re open to using faker module then you can try this approaches: stackoverflow/questions/76993621/… – mx0 Commented Mar 14 at 22:07
- The answers in that question require manually defining each field on each child model, whereas the solution I'm attempting here would automatically handle all child and their fields without manually defining them – Matt Commented Mar 18 at 3:20
2 Answers
Reset to default 1You can map them yourself.
class BaseModelWrapper(BaseModel):
@classmethod
def make_dummy(cls) -> BaseModelWrapper:
kwargs = {}
defaults = {
int: int(),
str: str(),
list[str]: list(),
int | None: None
}
for name, field in cls.model_fields.items():
if not field.is_required():
continue
kwargs[name] = defaults[field.annotation]
return cls(**kwargs)
You could something smart, like list() if isinstance(field.annotation, list) else None if field_is_optional(cls, name) else ...
where "is_optional" comes from https://stackoverflow/a/76397722/9072753 .
You can really just call the type and create, so you could do something like special handling for unions and optionals and have it auto-constructed:
def type_is_optional(t: type):
origin = get_origin(t)
#print(field_name, ":", field_type, origin)
if origin is Union:
return type(None) in get_args(t)
if origin is UnionType:
return type(None) in get_args(t)
return False
...
# How can I create values based on the field type?
kwargs[name] = (
None
if type_is_optional(field.annotation)
else get_args(field.annotation)[0]()
if get_origin(field.annotation) in (Union, UnionType)
else field.annotation()
)
I ultimately used @kamilcuk's answer to create the following method
from __future__ import annotations
from pydantic import BaseModel
from types import UnionType
from typing import Any, Union, get_args, get_origin
def is_union_type(t: type | UnionType) -> bool:
return get_origin(t) in [Union, UnionType]
class BaseModelWrapper(BaseModel):
@classmethod
def make_dummy(cls) -> Self:
kwargs = {}
for name, field in cls.model_fields.items():
if not field.is_required():
continue
if field.annotation is None:
kwargs[name] = None
continue
t = field.annotation
if is_union_type(t):
types: tuple[type[Any]] = get_args(t)
if type(None) in types:
kwargs[name] = None
continue
t = types[0]
try:
# Will error if given `list[str]` / `dict[str]`
if issubclass(t, BaseValidator):
kwargs[name] = t.make_dummy()
else:
kwargs[name] = t()
except TypeError:
kwargs[name] = t()
return cls(**kwargs)