Reputation: 2415
I have a pydantic model with many fields that are also pydantic models. I would like to make recursively all fields optional.
For example:
class MyModelE(BaseModel):
f: float
class MyModelA(BaseModel):
d: str
e: MyModelE
class MyModel(BaseModel):
a: MyModelA
b: int
I would like to have a metaclass or method to make all fields of MyModel
optional, including fields a
, b
, d
, e
, and f
.
The solutions described in this previously asked question only make fields that are non-pydantic models optional.
Make every fields as optional with Pydantic
Upvotes: 2
Views: 1092
Reputation: 1214
you can make a model optional by Optional
::
from typing import Optional
from pydantic import BaseModel
class MyModelE(BaseModel):
f: float
class MyModelA(BaseModel):
d: str
e: Optional[MyModelE] = None
class MyModel(BaseModel):
a: Optional[MyModelA] = None
b: int
Upvotes: 0
Reputation: 870
This is a solution for pydantic v2
.
It naturally uses depth first search down the hierarchy of composition and then with the field info found, it builds models with required=False
bottom-up towards the top.
Also, it does not use any low level hack that depends on the internal implementation of pydantic
objects, many of which are changed or not available in v2
.
from pydantic import BaseModel, create_model
class MyModelE(BaseModel):
f: float
class MyModelA(BaseModel):
d: str
e: MyModelE
class MyModel(BaseModel):
a: MyModelA
b: int
def is_pydantic_model(obj):
try:
return issubclass(obj, BaseModel)
except TypeError:
return False
def show_hierarchy(Model: BaseModel, indent: int=0):
for k, v in Model.model_fields.items():
print(f'{" "*indent}{Model.__name__}.{k}: '
f'type={getattr(v.annotation, "__name__", v.annotation)}, '
f'required={v.is_required()}')
if is_pydantic_model(v.annotation):
show_hierarchy(v.annotation, indent+2)
def unset_required(Model: BaseModel, name: str=None) -> BaseModel:
fields = {}
for k, v in Model.model_fields.items():
if is_pydantic_model(v.annotation):
fields[k] = (unset_required(v.annotation), None)
else:
fields[k] = (v.annotation, None)
return create_model(name if name is not None else Model.__name__, **fields)
show_hierarchy(MyModel)
print('')
UpdateModel = unset_required(MyModel, name='UpdateModel')
show_hierarchy(UpdateModel)
This results in
MyModel.a: type=MyModelA, required=True
MyModelA.d: type=str, required=True
MyModelA.e: type=MyModelE, required=True
MyModelE.f: type=float, required=True
MyModel.b: type=int, required=True
UpdateModel.a: type=MyModelA, required=False
MyModelA.d: type=str, required=False
MyModelA.e: type=MyModelE, required=False
MyModelE.f: type=float, required=False
UpdateModel.b: type=int, required=False
It can handle a model where the same sub-model is used multiple times at different levels as well.
class MyModelE(BaseModel):
f: float
class MyModelA(BaseModel):
d: str
e: MyModelE # MyModelE
class MyModel(BaseModel):
a: MyModelA
b: MyModelE # MyModelE
MyModel.a: type=MyModelA, required=True
MyModelA.d: type=str, required=True
MyModelA.e: type=MyModelE, required=True
MyModelE.f: type=float, required=True # MyModelE
MyModel.b: type=MyModelE, required=True
MyModelE.f: type=float, required=True # MyModelE
UpdateModel.a: type=MyModelA, required=False
MyModelA.d: type=str, required=False
MyModelA.e: type=MyModelE, required=False
MyModelE.f: type=float, required=False # MyModelE
UpdateModel.b: type=MyModelE, required=False
MyModelE.f: type=float, required=False # MyModelE
Upvotes: 1