Request Body parameters


When you need to send data from a client to your API, you send it as a request body. A request body is data sent by the client to your API. A response body is the data your API sends to the client.

Your API almost always has to send a response body. But clients don't necessarily need to send request bodies all the time. To declare a request body, you use Pydantic models with all their power and benefits.

Info

To send data, you should use one of: POST (the more common), PUT, DELETE or PATCH. Sending a body with a GET request has an undefined behavior in the specifications, nevertheless, it is supported by FastAPI, only for very complex/extreme use cases. As it is discouraged, the interactive docs with Swagger UI won't show the documentation for the body when using GET, and proxies in the middle might not support it.

For the example we will be building up in this section we would like to send a POST request with item data representing the following card:

Foofighters Card

The information needed to create the above card will have to be contained in a JSON object:

{
    "name": "Foo Fighters",
    "description": "The Pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
    "image": {
        "url": "http://example.com/cover.jpg",
        "name": "Foo Fighters The Pretender Cover"
    }
}

Later on we can then code a GET request to display the information in a card on a front-end application.

Import Pydantic's BaseModel

First steps first. You need to import BaseModel from pydantic:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

 















Create your data model

Then you declare your data model as a class that inherits from BaseModel.

Use standard Python types for all the attributes:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item




 
 
 
 
 








The same as when declaring query parameters, when a model attribute has a default value, it is not required. Otherwise, it is required. Use None to make it just optional.

For example, this model above declares a JSON object (or Python dict) like:

{
    "name": "Foo Fighters",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}

As description and tax are optional (with a default value of None), this JSON object would also be valid:

{
    "name": "Foo Fighters",
    "price": 45.2
}

Declare it as a parameter

To add it to your path operation, declare it the same way you declared path and query parameters and declare its type as the model you created, Item.:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item















 

With just that Python type declaration, FastAPI will:

  • Read the body of the request as JSON.
  • Convert the corresponding types (if needed).
  • Validate the data.
    • If the data is invalid, it will return a nice and clear error, indicating exactly where and what was the incorrect data.
  • Give you the received data in the parameter item.
    • As you declared it in the function to be of type Item, you will also have all the editor support (completion, etc) for all of the attributes and their types.
  • Generate JSON Schema definitions for your model, you can also use them anywhere else you like if it makes sense for your project.
  • Those schemas will be part of the generated OpenAPI schema, and used by the automatic documentation.

Tips

If you use PyCharm as your editor, you can use the Pydantic PyCharm Plugin. It improves editor support for Pydantic models, with:

  • auto-completion
  • type checks
  • refactoring
  • searching
  • inspections

You can also manually transform dictionaries to models and vice-versa:

Bodies of pure lists

If the top level value of the JSON body you expect is a JSON array (a Python list), you can declare the type in the parameter of the function, the same as in Pydantic models:

images: list[Image]

As in:

from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl

app = FastAPI()


class Image(BaseModel):
    url: HttpUrl
    name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
    return images












 

Use the model

Inside of the function, you can access all the attributes of the model object directly:

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict


















 


Mix Path, Query and body parameters

Of course you can mix Path, Query and request body parameter declarations freely and FastAPI will know what to do.

Request body + path parameters

You can declare path parameters and request body at the same time.

FastAPI will recognize that the function parameters that match path parameters should be taken from the path, and that function parameters that are declared to be Pydantic models should be taken from the request body.

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):














 
 

Request body + path + query parameters

You can also declare body, path and query parameters, all at the same time.

FastAPI will recognize each of them and take the data from the correct place.

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: str | None = None):















 

The function parameters will be recognized as follows:

  • If the parameter is also declared in the path, it will be used as a path parameter.
  • If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.
  • If the parameter is declared to be of the type of a Pydantic model, it will be interpreted as a request body.

Note

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

Expanding the model

With FastAPI, you can define, validate, document, and use arbitrarily deeply nested models (thanks to Pydantic).

List fields

You can define an attribute to be a subtype. For example, a Python list:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list = []


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results











 






This will make tags be a list, although it doesn't declare the type of the elements of the list.

List fields with type parameter

But Python has a specific way to declare lists with internal types, or "type parameters". To declare types that have type parameters (internal types), like list, dict, tuple.

So, in our example, we can make tags be specifically a "list of strings". Pass the internal type(s) as "type parameters" using square brackets: [ and ]:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results











 






But then we think about it, and realize that tags shouldn't repeat, they would probably be unique strings. And Python has a special data type for sets of unique items, the set.

Then we can declare tags as a set of strings:

class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()





 

With this, even if you receive a request with duplicate data, it will be converted to a set of unique items. And whenever you output that data, even if the source had duplicates, it will be output as a set of unique items.

Nested Models

Each attribute of a Pydantic model has a type but that type can itself be another Pydantic model. So, you can declare deeply nested JSON "objects" with specific attribute names, types and validations.

For example, we can define an Image model:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results






 
 
 















And then we can use it as the type of an attribute:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
    url: str
    name: str


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: set[str] = set()
    image: Image | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

















 






This would mean that FastAPI would expect a body similar to the one we started off with needing to make up our card in the front-end:

{
    "name": "Foo Fighters",
    "description": "The Pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
    "image": {
        "url": "http://example.com/cover.jpg",
        "name": "Foo Fighters The Pretender Cover"
    }
}

Foofighters Card

Again, doing just that declaration, with FastAPI you get:

  • Editor support (completion, etc), even for nested models
  • Data conversion
  • Data validation
  • Automatic documentation

Multiple body parameters

You can also declare multiple body parameters, e.g. item and user:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
    results = {"item_id": item_id, "item": item, "user": user}
    return results



















 


In this case, FastAPI will notice that there are more than one body parameters in the function (two parameters that are Pydantic models). It will then use the parameter names as keys (field names) in the body, and expect a body like:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    }
}

Note

Notice that even though the item was declared the same way as before, it is now expected to be inside of the body with a key item.

FastAPI will do the automatic conversion from the request, so that the parameter item receives its specific content and the same for user. It will perform the validation of the compound data, and will document it like that for the OpenAPI schema and automatic docs.

Singular values in body

Extending the previous model, you could decide that you want to have another key importance in the same body, besides the item and user.

If you declare it as is, because it is a singular value, FastAPI will assume that it is a query parameter.

But you can instruct FastAPI to treat it as another body key using Body:

from fastapi import Body, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None


class User(BaseModel):
    username: str
    full_name: str | None = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body()):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results



















 


In this case, FastAPI will expect a body like:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}

Again, it will convert the data types, validate, document, etc.

Last update: 10/10/2022, 11:55:39 PM