morfys
morfys

Reputation: 2415

Make all fields with nested pydantic models optional recursively

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

Answers (2)

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

jung rhew
jung rhew

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

Related Questions