Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to exclude Optional unset values from a Pydantic model using FastAPI?

I have this model:

class Text(BaseModel):
    id: str
    text: str = None


class TextsRequest(BaseModel):
    data: list[Text]
    n_processes: Union[int, None]

So, I want to be able to take requests like:

{"data": ["id": "1", "text": "The text 1"], "n_processes": 8} 

and

{"data": ["id": "1", "text": "The text 1"]}.

Right now, in the second case I get:

{'data': [{'id': '1', 'text': 'The text 1'}], 'n_processes': None}

using this code:

app = FastAPI()

@app.post("/make_post/", response_model_exclude_none=True)
async def create_graph(request: TextsRequest):
    input_data = jsonable_encoder(request)

So how can I exclude n_processes here?

like image 966
ldevyataykina Avatar asked Dec 17 '25 23:12

ldevyataykina


2 Answers

Pydantic provides the following arguments for exporting models using the model.dict(...) method (Update: model.dict(...) has been deprecated and replaced by model.model_dump(...)). Those parameters are as follows:

exclude_unset: whether fields which were not explicitly set when creating the model should be excluded from the returned dictionary; default False

exclude_none: whether fields which are equal to None should be excluded from the returned dictionary; default False

Since you are refering to excluding optional unset parameters, you can use the first method (i.e., exclude_unset). This is useful when one would like to exclude a parameter only if it has not been set to either some value or None.

The exclude_none argument, however, ignores that fact that an attribute may have been intentionally set to None, and hence, excludes it from the returned dictionary.

Example:

from pydantic import BaseModel
from typing import List, Union


class Text(BaseModel):
    id: str
    text: str = None


class TextsRequest(BaseModel):
    data: List[Text]    # in Python 3.9+ you can use:  data: list[Text]
    n_processes: Union[int, None] = None


t = TextsRequest(**{'data': [{'id': '1', 'text': 'The text 1'}], 'n_processes': None})
print(t.model_dump(exclude_none=True))
#> {'data': [{'id': '1', 'text': 'The text 1'}]}

print(t.model_dump(exclude_unset=True))
#> {'data': [{'id': '1', 'text': 'The text 1'}], 'n_processes': None}

Excluding Unset or None parameters from the response

If what you needed is excluding Unset or None parameters from the endpoint's response, without necessarily calling model.dict(...) (or, in Pydantic V2 model.model_dump(...)) inside the endpoint on your own, you could instead use the endpoint's decorator parameter response_model_exclude_unset or response_model_exclude_none (see the relevant documentation and related answers here and here). To achieve that, FastAPI, behind the scenes, uses the aforementioned model.dict(...) (or, in Pydantic V2 model.model_dump(...)) method with its exclude_unset or exclude_none parameter.

Examples:

@app.post("/create", response_model_exclude_unset=True)
async def create_graph(t: TextsRequest):
    return t

or

@app.post("/create", response_model_exclude_none=True)
async def create_graph(t: TextsRequest):
    return t

About Optional Parameters

Using Union[int, None] is the same as using Optional[int] (both are equivalent). The most important part, however, to make a parameter optional is the part = None.

As per FastAPI documentation (see admonition Note and Info in the link provided):

Note

FastAPI will know that the value of q is not required because of the default value = None.

The Union in Union[str, None] will allow your editor to give you better support and detect errors.

Info

Have in mind that the most important part to make a parameter optional is the part: = None, as it will use that None as the default value, and that way make the parameter not required.

The Union[str, None] part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.

Hence, regardless of the option you may choose to use, if it is not followed by the = None part, FastAPI won't know that the value of the parameter is optional, and hence, the user will have to provide some value for it. One can also check that through the auto-generated API docs at http://127.0.0.1:8000/docs, where the parameter or request body will appear as a Required field.

For example, any of the below would require the user to pass some body content in their request for the TextsRequest model:

@app.post("/upload")
def upload(t: Union[TextsRequest, None]):
    pass

@app.post("/upload")
def upload(t: Optional[TextsRequest]):
    pass

If, however, the above TextsRequest definitions were succeeded by = None, for example:

@app.post("/upload")
def upload(t: Union[TextsRequest, None] = None):
    pass

@app.post("/upload")
def upload(t: Optional[TextsRequest] = None):
    pass
    
@app.post("/upload")
def upload(t: TextsRequest = None): # this should work as well
    pass

the parameter (or body) would be optional, as = None would tell FastAPI that this parameter is not required.

In Python 3.10+

The good news is that in Python 3.10 and above, you don't have to worry about names like Optional and Union, as you can simply use the vertical bar | (also called bitwise or operator, but that meaning is not relevant here) to define an optional parameter (or simply, unions of types). However, the same rule applies to this option as well, i.e., you would still need to add the = None part, if you would like to make the parameter optional (as demonstrated in the example given below).

Example:

@app.post("/upload")
def upload(t: TextsRequest | None = None):
    pass
like image 50
Chris Avatar answered Dec 19 '25 15:12

Chris


Since Pydantic >= 2.0 deprecates model.dict() use model.model_dump(...) instead.

You can use exclude_none param of Pydantic's model.dict(...):

class Text(BaseModel):
    id: str
    text: str = None


class TextsRequest(BaseModel):
    data: list[Text]
    n_processes: Optional[int]


request = TextsRequest(**{"data": [{"id": "1", "text": "The text 1"}]})
print(request.dict(exclude_none=True))

Output:

{'data': [{'id': '1', 'text': 'The text 1'}]}

Also, it's more idiomatic to write Optional[int] instead of Union[int, None].

like image 35
funnydman Avatar answered Dec 19 '25 13:12

funnydman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!