Saltar al contenido

Cuerpo de la Petición

Cuando necesitas enviar datos desde un cliente (digamos, un navegador) a tu API, los envías como un cuerpo de petición.

Un cuerpo de petición son los datos enviados por el cliente a tu API. Un cuerpo de respuesta son los datos que tu API envía al cliente.

Tu API casi siempre tiene que enviar un cuerpo de respuesta. Pero los clientes no necesariamente necesitan enviar cuerpos de petición todo el tiempo, a veces solo solicitan una ruta, quizás con algunos parámetros de query, pero no envían un cuerpo.

Para declarar un cuerpo de petición, usas modelos de Pydantic con todo su poder y beneficios.

Nota

Para enviar datos, deberías usar uno de: POST (el más común), PUT, DELETE o PATCH.

Enviar un cuerpo con una petición GET tiene un comportamiento indefinido en las especificaciones, sin embargo, está soportado por FastAPI, solo para casos de uso muy complejos/extremos.

Como está desaconsejado, la documentación interactiva con Swagger UI no mostrará la documentación del cuerpo cuando se usa GET, y los proxies intermedios podrían no soportarlo.

Importar el BaseModel de Pydantic

Primero, necesitas importar BaseModel desde 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

Crear tu modelo de datos

Luego declaras tu modelo de datos como una clase que hereda de BaseModel.

Usa tipos estándar de Python para todos los atributos:

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

Al igual que al declarar parámetros de query, cuando un atributo del modelo tiene un valor por defecto, no es requerido. De lo contrario, es requerido. Usa None para hacerlo solo opcional.

Por ejemplo, este modelo de arriba declara un "object" JSON (o Python dict) como:

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

...como description y tax son opcionales (con un valor por defecto de None), este "object" JSON también sería válido:

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

Declararlo como parámetro

Para añadirlo a tu operación de ruta, decláralo de la misma manera que declaraste los parámetros de ruta y de query:

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

...y declara su tipo como el modelo que creaste, Item.

Resultados

Con solo esa declaración de tipo Python, FastAPI hará:

  • Leer el cuerpo de la petición como JSON.
  • Convertir los tipos correspondientes (si es necesario).
  • Validate the data.
    • Si los datos son inválidos, devolverá un error claro y bonito, indicando exactamente dónde y cuál fue el dato incorrecto.
  • Give you the received data in the parameter item.
    • Como lo declaraste en la función como de tipo Item, también tendrás todo el soporte del editor (completado, etc) para todos los atributos y sus tipos.
  • Generar definiciones de JSON Schema para tu modelo, también puedes usarlas en cualquier otro lugar que quieras si tiene sentido para tu proyecto.
  • Esos esquemas serán parte del esquema OpenAPI generado, y usados por las UIs de documentación automática.

Documentación automática

Los JSON Schemas de tus modelos serán parte de tu esquema OpenAPI generado, y se mostrarán en la documentación interactiva de la API:

Y también serán usados en la documentación de la API dentro de cada operación de ruta que los necesite:

Soporte del editor

En tu editor, dentro de tu función obtendrás type hints y completado en todas partes (esto no pasaría si recibieras un dict en lugar de un modelo de Pydantic):

También obtienes chequeos de errores para operaciones de tipo incorrecto:

Esto no es por casualidad, todo el framework fue construido alrededor de ese diseño.

Y fue probado exhaustivamente en la fase de diseño, antes de cualquier implementación, para asegurar que funcionaría con todos los editores.

Hubo incluso algunos cambios en el propio Pydantic para soportar esto.

Las capturas de pantalla anteriores fueron tomadas con Visual Studio Code.

Pero obtendrías el mismo soporte del editor con PyCharm y la mayoría de los otros editores de Python:

Consejo

Si usas PyCharm como tu editor, puedes usar el Pydantic PyCharm Plugin.

Mejora el soporte del editor para los modelos de Pydantic, con:

  • auto-completado
  • chequeo de tipos
  • refactoring
  • búsqueda
  • inspecciones

Usar el modelo

Dentro de la función, puedes acceder a todos los atributos del objeto modelo directamente:

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.model_dump()
    if item.tax is not None:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

Cuerpo de petición + parámetros de ruta

Puedes declarar parámetros de ruta y cuerpo de petición al mismo tiempo.

FastAPI reconocerá que los parámetros de la función que coinciden con parámetros de ruta deben ser tomados de la ruta, y que los parámetros de la función que se declaran como modelos de Pydantic deben ser tomados del cuerpo de la petición.

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 update_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.model_dump()}

Cuerpo de petición + parámetros de ruta + parámetros de query

También puedes declarar parámetros de body, path y query, todos al mismo tiempo.

FastAPI reconocerá cada uno de ellos y tomará los datos del lugar correcto.

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 update_item(item_id: int, item: Item, q: str | None = None):
    result = {"item_id": item_id, **item.model_dump()}
    if q:
        result.update({"q": q})
    return result

Los parámetros de la función serán reconocidos de la siguiente manera:

  • Si el parámetro también está declarado en la ruta, se usará como un parámetro de ruta.
  • Si el parámetro es de un tipo singular (como int, float, str, bool, etc) se interpretará como un parámetro de query.
  • Si el parámetro se declara como de tipo de un modelo de Pydantic, se interpretará como un cuerpo de petición.

Nota

FastAPI sabrá que el valor de q no es requerido por el valor por defecto = None.

El str | None no es usado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de = None.

Pero añadir las anotaciones de tipo permitirá que tu editor te dé mejor soporte y detecte errores.

Sin Pydantic

Si no quieres usar modelos de Pydantic, también puedes usar parámetros Body. Mira la documentación de Body - Multiple Parameters: Singular values in body.