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