Search code examples
pythonceleryfastapipydantic

Values in Pydantic 2.4 are not converting to a model


I am using Fastapi, Celery and Pydantic(2.4). The issue I'm encountering is that when I attempt to create a Model instance (args = Model(**args)) from a dictionary, the setting value is not being converted into an instance of the SettingItem class; instead, it becomes a dict.

How can I make the setting an instance of SettingItem?

from typing import List

from pydantic import BaseModel


class SettingItem(BaseModel):
    setting_id: str
    alias: str
    date: str


class Model(BaseModel):
    id: str
    setting: List[SettingItem]

@app.task
def get_data(args: Model):
    if isinstance(args,dict)
       args = Model.model_construct(**args)
    
    # this line throws an exception. The root cause is that args.setting is a dict instead of an instance of SettingItem
    dates = [x.date for x in args.setting]
    ...

@api.get("/")
def run(args:Model)
    get_data.delay(args.model_dumps())

I have tried Model.model_validate_json() and Model.parse_obj(),none of them works


Solution

  • Basically, with the following test, you can see the setting type is SettingItem:

    d = dict(setting=[dict(setting_id='1', alias='a', date='2023')], id="1")
    m = Model(**d)
    print(type(m.setting[0]))
    

    Out:

    __main__.SettingItem
    

    So there's nothing to do with your pydantic models.

    It seems the problem is related to this line get_data.delay(args.model_dumps()) as you converted the Model to a dict yourself. Therefore, obviously, you would get a dict within the get_data() method not the Model.

    To solve the issue, you would need whether to pass the args without any conversion or convert it back to Model within the get_data() method as follows:

    @app.task
    def get_data(args: dict):
        if isinstance(args, dict)
           args = Model.model_validate(args)
        ...
    

    To ensure it works look at this:

    d = dict(setting=[dict(setting_id='1', alias='a', date='2023')], id="1")
    m = Model(**d)
    dd = m.model_dump()  # the thing you have done in run() method
    mm = Model.model_validate(dd)  # convert it back again from dict to Model
    print(type(mm.setting[0]))
    

    Out:

    __main__.SettingItem
    

    [NOTE]:

    • parse_obj() is being deprecated in pydantic version 2, so it's better to use model_validate() instead.