Saltar al contenido

Declarar Datos de Ejemplo de Petición

Puedes declarar ejemplos de los datos que tu aplicación puede recibir.

Aquí hay varias formas de hacerlo.

Datos extra de JSON Schema en modelos de Pydantic

Puedes declarar examples para un modelo de Pydantic que serán agregados al JSON Schema generado.

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

    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                }
            ]
        }
    }


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

Esa información extra se agregará tal cual al JSON Schema de salida para ese modelo, y se usará en la documentación de la API.

Puedes usar el atributo model_config que toma un dict como se describe en la documentación de Pydantic: Configuration.

Puedes establecer "json_schema_extra" con un dict que contenga cualquier dato adicional que te gustaría que aparezca en el JSON Schema generado, incluyendo examples.

Consejo

Puedes usar la misma técnica para extender el JSON Schema y agregar tu propia información extra personalizada.

Por ejemplo, podrías usarlo para agregar metadatos para una interfaz de usuario frontend, etc.

Nota

OpenAPI 3.1.0 (usado desde FastAPI 0.99.0) agregó soporte para examples, que es parte del estándar JSON Schema.

Antes de eso, solo soportaba la palabra clave example con un único ejemplo. Esto todavía es soportado por OpenAPI 3.1.0, pero está deprecado y no es parte del estándar JSON Schema. Por lo que se te anima a migrar de example a examples. 🤓

Puedes leer más al final de esta página.

Argumentos adicionales de Field

Cuando usas Field() con modelos de Pydantic, también puedes declarar examples adicionales:

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str = Field(examples=["Foo"])
    description: str | None = Field(default=None, examples=["A very nice Item"])
    price: float = Field(examples=[35.4])
    tax: float | None = Field(default=None, examples=[3.2])


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

examples en JSON Schema - OpenAPI

Cuando uses cualquiera de:

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
  • Form()
  • File()

también puedes declarar un grupo de examples con información adicional que se agregará a sus JSON Schemas dentro de OpenAPI.

Body con examples

Aquí pasamos examples conteniendo un ejemplo de los datos esperados en Body():

from typing import Annotated

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


@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Annotated[
        Item,
        Body(
            examples=[
                {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                }
            ],
        ),
    ],
):
    results = {"item_id": item_id, "item": item}
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

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


@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    item: Item = Body(
        examples=[
            {
                "name": "Foo",
                "description": "A very nice Item",
                "price": 35.4,
                "tax": 3.2,
            }
        ],
    ),
):
    results = {"item_id": item_id, "item": item}
    return results

Ejemplo en la interfaz de documentación

Con cualquiera de los métodos anteriores se vería así en /docs:

Body con múltiples examples

Por supuesto, también puedes pasar múltiples examples:

from typing import Annotated

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


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Annotated[
        Item,
        Body(
            examples=[
                {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                },
                {
                    "name": "Bar",
                    "price": "35.4",
                },
                {
                    "name": "Baz",
                    "price": "thirty five point four",
                },
            ],
        ),
    ],
):
    results = {"item_id": item_id, "item": item}
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

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


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item = Body(
        examples=[
            {
                "name": "Foo",
                "description": "A very nice Item",
                "price": 35.4,
                "tax": 3.2,
            },
            {
                "name": "Bar",
                "price": "35.4",
            },
            {
                "name": "Baz",
                "price": "thirty five point four",
            },
        ],
    ),
):
    results = {"item_id": item_id, "item": item}
    return results

Cuando haces esto, los ejemplos serán parte del JSON Schema interno para esos datos del body.

Sin embargo, al momento de escribir esto, Swagger UI, la herramienta encargada de mostrar la interfaz de documentación, no soporta mostrar múltiples ejemplos para los datos en JSON Schema. Pero lee a continuación para una solución alternativa.

examples específico de OpenAPI

Desde antes de que JSON Schema soportara examples, OpenAPI tenía soporte para un campo diferente también llamado examples.

Este examples específico de OpenAPI va en otra sección en la especificación OpenAPI. Va en los detalles de cada path operation, no dentro de cada JSON Schema.

Y Swagger UI ha soportado este campo particular de examples durante un tiempo. Así que puedes usarlo para mostrar diferentes ejemplos en la interfaz de documentación.

La estructura de este campo examples específico de OpenAPI es un dict con múltiples ejemplos (en lugar de una list), cada uno con información extra que también se agregará a OpenAPI.

Esto no va dentro de cada JSON Schema contenido en OpenAPI, esto va fuera, en la path operation directamente.

Usar el Parámetro openapi_examples

Puedes declarar los examples específicos de OpenAPI en FastAPI con el parámetro openapi_examples para:

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
  • Form()
  • File()

Las claves del dict identifican cada ejemplo, y cada valor es otro dict.

Cada dict de ejemplo específico en los examples puede contener:

  • summary: Descripción corta para el ejemplo.
  • description: Una descripción larga que puede contener texto Markdown.
  • value: Este es el ejemplo actual que se muestra, por ejemplo, un dict.
  • externalValue: alternativa a value, una URL que apunta al ejemplo. Aunque esto podría no estar soportado por tantas herramientas como value.

Puedes usarlo así:

from typing import Annotated

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


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Annotated[
        Item,
        Body(
            openapi_examples={
                "normal": {
                    "summary": "A normal example",
                    "description": "A **normal** item works correctly.",
                    "value": {
                        "name": "Foo",
                        "description": "A very nice Item",
                        "price": 35.4,
                        "tax": 3.2,
                    },
                },
                "converted": {
                    "summary": "An example with converted data",
                    "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
                    "value": {
                        "name": "Bar",
                        "price": "35.4",
                    },
                },
                "invalid": {
                    "summary": "Invalid data is rejected with an error",
                    "value": {
                        "name": "Baz",
                        "price": "thirty five point four",
                    },
                },
            },
        ),
    ],
):
    results = {"item_id": item_id, "item": item}
    return results
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

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


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item = Body(
        openapi_examples={
            "normal": {
                "summary": "A normal example",
                "description": "A **normal** item works correctly.",
                "value": {
                    "name": "Foo",
                    "description": "A very nice Item",
                    "price": 35.4,
                    "tax": 3.2,
                },
            },
            "converted": {
                "summary": "An example with converted data",
                "description": "FastAPI can convert price `strings` to actual `numbers` automatically",
                "value": {
                    "name": "Bar",
                    "price": "35.4",
                },
            },
            "invalid": {
                "summary": "Invalid data is rejected with an error",
                "value": {
                    "name": "Baz",
                    "price": "thirty five point four",
                },
            },
        },
    ),
):
    results = {"item_id": item_id, "item": item}
    return results

Ejemplos de OpenAPI en la Interfaz de Documentación

Con openapi_examples agregado a Body() el /docs se vería así:

Detalles Técnicos

Consejo

Si ya estás usando la versión 0.99.0 o superior de FastAPI, probablemente puedes saltarte estos detalles.

Son más relevantes para versiones anteriores, antes de que OpenAPI 3.1.0 estuviera disponible.

Puedes considerar esto una breve lección de historia de OpenAPI y JSON Schema. 🤓

Aviso

Estos son detalles muy técnicos sobre los estándares JSON Schema y OpenAPI.

Si las ideas anteriores ya te funcionan, eso puede ser suficiente, y probablemente no necesites estos detalles, siéntete libre de saltarlos.

Antes de OpenAPI 3.1.0, OpenAPI usaba una versión más antigua y modificada de JSON Schema.

JSON Schema no tenía examples, así que OpenAPI agregó su propio campo example a su propia versión modificada.

OpenAPI también agregó campos example y examples a otras partes de la especificación:

Nota

Este antiguo parámetro examples específico de OpenAPI es ahora openapi_examples desde FastAPI 0.103.0.

El campo examples de JSON Schema

Pero entonces JSON Schema agregó un campo examples a una nueva versión de la especificación.

Y luego el nuevo OpenAPI 3.1.0 se basó en la última versión (JSON Schema 2020-12) que incluía este nuevo campo examples.

Y ahora este nuevo campo examples toma precedencia sobre el antiguo campo example único (y personalizado), que ahora está deprecado.

Este nuevo campo examples en JSON Schema es solo una list de ejemplos, no un dict con metadatos extra como en los otros lugares de OpenAPI (descritos anteriormente).

Nota

Incluso después de que se lanzó OpenAPI 3.1.0 con esta nueva integración más simple con JSON Schema, durante un tiempo, Swagger UI, la herramienta que proporciona la documentación automática, no soportaba OpenAPI 3.1.0 (lo hace desde la versión 5.0.0 🎉).

Por eso, las versiones de FastAPI anteriores a 0.99.0 todavía usaban versiones de OpenAPI inferiores a 3.1.0.

examples en Pydantic y FastAPI

Cuando agregas examples dentro de un modelo de Pydantic, usando schema_extra o Field(examples=["something"]) ese ejemplo se agrega al JSON Schema para ese modelo de Pydantic.

Y ese JSON Schema del modelo de Pydantic se incluye en el OpenAPI de tu API, y luego se usa en la interfaz de documentación.

En versiones de FastAPI anteriores a 0.99.0 (0.99.0 y superior usan el nuevo OpenAPI 3.1.0) cuando usabas example o examples con cualquiera de las otras utilidades (Query(), Body(), etc.) esos ejemplos no se agregaban al JSON Schema que describe esos datos (ni siquiera a la versión propia de OpenAPI de JSON Schema), se agregaban directamente a la declaración de la path operation en OpenAPI (fuera de las partes de OpenAPI que usan JSON Schema).

Pero ahora que FastAPI 0.99.0 y superior usa OpenAPI 3.1.0, que usa JSON Schema 2020-12, y Swagger UI 5.0.0 y superior, todo es más consistente y los ejemplos están incluidos en JSON Schema.

Swagger UI y examples específico de OpenAPI

Ahora, como Swagger UI no soportaba múltiples ejemplos de JSON Schema (a fecha de 2023-08-26), los usuarios no tenían una forma de mostrar múltiples ejemplos en la documentación.

Para solucionar eso, FastAPI 0.103.0 agregó soporte para declarar el mismo antiguo campo examples específico de OpenAPI con el nuevo parámetro openapi_examples. 🤓

Resumen

Solía decir que no me gustaba mucho la historia... y mírame ahora dando lecciones de "historia de la tecnología". 😅

En resumen, actualiza a FastAPI 0.99.0 o superior, y las cosas son mucho más simples, consistentes e intuitivas, y no tienes que conocer todos estos detalles históricos. 😎