Search code examples
pythonpydantic

Pydantic: Detect if a field value is missing or given as null


I want to allow users to selectively update fields using PUT calls. On the pydantic model, I have made the fields Optional. In the FastAPI handler if the model attribute is None, then the field was not given and I do not update it.

The problem with this approach is that there is no way for the client to "blank out" a field that isn't required for certain types.

In particular, I have date fields that I want clients to be able to clear by sending in a null in the JSON. How can I detect the difference between the client sending null or the client not sending the field/value at all? The model attribute is just None in either case.


Solution

  • Pydantic V2

    Pydantic V2 is available since June 30, 2023

    The .dict() method has been removed in V2. In order to get a dictionary out of a BaseModel instance, one must use the model_dump() method instead:

    from __future__ import annotations
    from pydantic import BaseModel
    
    
    class MyModel(BaseModel):
        foo: int | None = None
        bar: int | None = None
    
    baz = MyModel(foo=None)
    assert baz.model_dump(exclude_unset=True) == {"foo": None}
    
    baz = MyModel(bar=None)
    assert baz.model_dump(exclude_unset=True) == {"bar": None}
    

    Pydantic V1

    The pydantic documentation desccribes two options that can be used with the .dict() method of models.

    • exclude_unset: whether fields which were not explicitly set when creating the model should be excluded from the returned dictionary; default False. Prior to v1.0, exclude_unset was known as skip_defaults; use of skip_defaults is now deprecated

    • exclude_defaults: whether fields which are equal to their default values (whether set or otherwise) should be excluded from the returned dictionary; default False

    So you can create a model class with optional fields:

    from typing import Optional
    from pydantic import BaseModel
    
    
    class MyModel(BaseModel):
        foo: Optional[int] = None
        bar: Optional[int] = None
    

    And still generate a dict with fields explicitely set to None, but without default values:

    baz = MyModel(foo=None)
    assert baz.dict(exclude_unset=True) == {"foo": None}
    
    baz = MyModel(bar=None)
    assert baz.dict(exclude_unset=True) == {"bar": None}