Saltar al contenido

Esquemas de OpenAPI Separados para Entrada y Salida o No

Desde que se lanzó Pydantic v2, el OpenAPI generado es un poco más exacto y correcto que antes. 😎

De hecho, en algunos casos, incluso tendrá dos JSON Schemas en OpenAPI para el mismo modelo de Pydantic, para entrada y salida, dependiendo de si tienen valores por defecto.

Veamos cómo funciona eso y cómo cambiarlo si necesitas hacerlo.

Modelos de Pydantic para Entrada y Salida

Digamos que tienes un modelo de Pydantic con valores por defecto, como este:

from fastapi import FastAPI
from pydantic import BaseModel


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

# Code below omitted 👇
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

Modelo para Entrada

Si usas este modelo como entrada como aquí:

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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

# Code below omitted 👇
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...entonces el campo description no será requerido. Porque tiene un valor por defecto de None.

Modelo de Entrada en Docs

Puedes confirmar que en la documentación, el campo description no tiene un asterisco rojo, no está marcado como requerido:

Modelo para Salida

Pero si usas el mismo modelo como salida, como aquí:

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI()


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

...entonces como description tiene un valor por defecto, si no devuelves nada para ese campo, aún tendrá ese valor por defecto.

Modelo para Datos de Respuesta de Salida

Si interactúas con la documentación y revisas la respuesta, aunque el código no añadió nada en uno de los campos description, la respuesta JSON contiene el valor por defecto (null):

Esto significa que siempre tendrá un valor, solo que a veces el valor podría ser None (o null en JSON).

Eso significa que los clientes que usan tu API no tienen que verificar si el valor existe o no, pueden asumir que el campo siempre estará ahí, solo que en algunos casos tendrá el valor por defecto de None.

La forma de describir esto en OpenAPI es marcar ese campo como requerido, porque siempre estará ahí.

Por eso, el JSON Schema de un modelo puede ser diferente dependiendo de si se usa para entrada o salida:

  • para entrada description no será requerido
  • para salida será requerido (y posiblemente None, o en términos de JSON, null)

Modelo de Salida en Docs

También puedes revisar el modelo de salida en la documentación, tanto name como description están marcados como requeridos con un asterisco rojo:

Modelo de Entrada y Salida en Docs

Y si revisas todos los Esquemas disponibles (JSON Schemas) en OpenAPI, verás que hay dos, uno Item-Input y uno Item-Output.

Para Item-Input, description es no requerido, no tiene un asterisco rojo.

Pero para Item-Output, description es requerido, tiene un asterisco rojo.

Con esta característica de Pydantic v2, la documentación de tu API es más precisa, y si tienes clientes y SDKs autogenerados, también serán más precisos, con una mejor experiencia de desarrollador y consistencia. 🎉

No Separar Esquemas

Ahora, hay algunos casos en los que podrías querer tener el mismo esquema para entrada y salida.

Probablemente el principal caso de uso para esto es si ya tienes código/SDKs autogenerados y no quieres actualizar todo el código/SDKs autogenerados todavía, probablemente querrás hacerlo en algún momento, pero quizás no ahora.

En ese caso, puedes deshabilitar esta característica en FastAPI, con el parámetro separate_input_output_schemas=False.

Nota

El soporte para separate_input_output_schemas fue añadido en FastAPI 0.102.0. 🤓

from fastapi import FastAPI
from pydantic import BaseModel


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


app = FastAPI(separate_input_output_schemas=False)


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


@app.get("/items/")
def read_items() -> list[Item]:
    return [
        Item(
            name="Portal Gun",
            description="Device to travel through the multi-rick-verse",
        ),
        Item(name="Plumbus"),
    ]

Mismo Esquema para Modelos de Entrada y Salida en Docs

Y ahora habrá un único esquema para entrada y salida para el modelo, solo Item, y tendrá description como no requerido: