Search code examples
pythonfastapipydanticsqlmodel

SQLModel behaves differently from Pydantic BaseModel in exclude_unset


I have the following code snippet

class Model(BaseModel):
    is_required: float
    a_float: Optional[float] = None
    k: Optional[int] = None


k = Model(
    **{
        "is_required": 0.1,
        "a_float": 1.2,
    }
)
print(k.dict()) #{'is_required': 0.1, 'a_float': 1.2, 'k': None}
print(k.dict(exclude_unset=True)) #{'is_required': 0.1, 'a_float': 1.2}

This is understandable. But once I switch to SQLModel using the following code, the result changed for exclude_unset.

class Model(SQLModel):
    is_required: float
    a_float: Optional[float] = None
    k: Optional[int] = None

k = Model(
    **{
        "is_required": 0.1,
        "a_float": 1.2,
    }
)
print(k.dict()) #{'is_required': 0.1, 'a_float': 1.2, 'k': None}
print(k.dict(exclude_unset=True)) #{'is_required': 0.1, 'a_float': 1.2, 'k': None}

Why does this happen, and is there a way for me to get a dict where unsets are not included in the export using dict()?


Solution

  • EDIT: this has been fixes as of SQLModel version 0.0.7.

    Original answer: I looked into this a bit, and as of today (version 0.0.6), SQLModel.dict(exclude_unset=True) simply does not work as intended, at least when instantiated using the normal constructor. There is an open GitHub issue about this, and a PR that addresses it.

    My guess would be that FastAPI (which clearly is the "intended usecase" of SQLModel) uses some variant of the from_orm method, because this code works:

    from types import SimpleNamespace
    from typing import Optional
    
    from sqlmodel import SQLModel
    
    class Model(SQLModel):
        is_required: float
        kasdf: Optional[int] = None
        a_float: Optional[float] = None
    
    k = Model.from_orm(SimpleNamespace(is_required=0.1, a_float=1.2))
    print(k2.dict())  # {'is_required': 0.1, 'a_float': 1.2, 'k': None}
    print(k2.dict(exclude_unset=True))  # {'is_required': 0.1, 'a_float': 1.2}
    

    Here I'm using SimpleNamespace, which is simply a way to create an object with the provided names as fields

    s = SimpleNamespace(foo=3, bar="value")
    print(s.foo)  # 3
    print(s.bar)  # 'value'