Skip to content

#218: Send Data to FastAPI

So far, we used the GET method to ask FastAPI for data. Now it is time to send data to FastAPI and do some work with it.

Pydantic models

FastAPI makes is super simple for us if we follow along and use Pydantic models for our endpoints – while it gets incredibly annoying if we try to go a different way. Therefore, let us follow the official path before we try another way at the end of this post.

Given we have this data snipped that we want to send to our API:

1
2
3
4
5
data = {
        "a": 13,
        "b": "ABC",
        "c": True
    }

We can create a minimalistic Pydantic model with this code:

1
2
3
4
5
6
from pydantic import BaseModel

class Item(BaseModel):
    a: int
    b: str
    c: bool

We can create an instance of our Item class by unpacking our data dictionary:

1
2
3
4
5
6
7
>>> item = Item(**data)
>>> item.a
13
>>> item.b
'ABC'
>>> item.c
True

If we try to use data that does not match our description, Pydantic will throw an exception:

wrong = Item(a="Hello", b="World", c=False)
Traceback (most recent call last):
pydantic_core._pydantic_core.ValidationError: 1 validation error for Item a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value=Hello, input_type=str]
For further information visit https://errors.pydantic.dev/2.5/v/int_parsing

With only 4 lines of class definition, we got our model and a data validation that checks if the parameters match our expected data types.

Use the Pydantic model with our endpoint

For our endpoint we can use the POST method and use our model as the parameter:

1
2
3
@app.post("/check")
async def check(item: Item):
    return item

To test if everything works as expected, we can call our /check endpoint with this test:

1
2
3
4
5
6
7
8
9
def test_check_with_model():
    data = {
        "a": 13,
        "b": "ABC",
        "c": True
    }
    response = client.post("/check", json=data)
    assert response.status_code == 200
    assert response.json() == data

If you want to try it in your browser, head to the Swagger documentation in http://127.0.0.1:8000/docs and select the /check endpoint:

We can try our post method with the Swagger interface.

The hard way without Pydantic

This part is only to show you that you can ignore Pydantic and still get data from the request body into your endpoints. However, the code not only looks ugly, but it is also a lot more work:

1
2
3
4
5
6
@app.post("/check2")
async def check(a: Annotated[int, Body()], 
                b: Annotated[str, Body()], 
                c: Annotated[bool, Body()]):

    return {"a": a, "b": b, "c": c}

We can reuse our test from above and only need to change the name of the endpoint we send our data to:

1
2
3
4
5
6
7
8
9
def test_check2_without_model():
    data = {
        "a": 13,
        "b": "ABC",
        "c": True
    }
    response = client.post("/check2", json=data)
    assert response.status_code == 200
    assert response.json() == data

Next

If we use Pydantic for our models we can keep our FastAPI endpoints nice and compact. While it takes a bit of time to get used to that way, it is much faster than to go the extra mile with Annotated and Body().

As soon as we use models we end up with a mess in our code. Next week we look at a better way to structure our API.