Saltar a contenido

Responses Adicionales en OpenAPI

Advertencia

Este es un tema bastante avanzado.

Si estás comenzando con FastAPI, puede que no lo necesites.

Puedes declarar responses adicionales, con códigos de estado adicionales, media types, descripciones, etc.

Esos responses adicionales se incluirán en el esquema de OpenAPI, por lo que también aparecerán en la documentación de la API.

Pero para esos responses adicionales tienes que asegurarte de devolver un Response como JSONResponse directamente, con tu código de estado y contenido.

Response Adicional con model

Puedes pasar a tus decoradores de path operation un parámetro responses.

Recibe un dict: las claves son los códigos de estado para cada response (como 200), y los valores son otros dicts con la información para cada uno de ellos.

Cada uno de esos dicts de response puede tener una clave model, conteniendo un modelo de Pydantic, así como response_model.

FastAPI tomará ese modelo, generará su JSON Schema y lo incluirá en el lugar correcto en OpenAPI.

Por ejemplo, para declarar otro response con un código de estado 404 y un modelo Pydantic Message, puedes escribir:

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


class Message(BaseModel):
    message: str


app = FastAPI()


@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
async def read_item(item_id: str):
    if item_id == "foo":
        return {"id": "foo", "value": "there goes my hero"}
    return JSONResponse(status_code=404, content={"message": "Item not found"})

Nota

Ten en cuenta que debes devolver el JSONResponse directamente.

Información

La clave model no es parte de OpenAPI.

FastAPI tomará el modelo de Pydantic de allí, generará el JSON Schema y lo colocará en el lugar correcto.

El lugar correcto es:

  • En la clave content, que tiene como valor otro objeto JSON (dict) que contiene:
  • Una clave con el media type, por ejemplo, application/json, que contiene como valor otro objeto JSON, que contiene:
    • Una clave schema, que tiene como valor el JSON Schema del modelo, aquí es el lugar correcto.
      • FastAPI agrega una referencia aquí a los JSON Schemas globales en otro lugar de tu OpenAPI en lugar de incluirlo directamente. De este modo, otras aplicaciones y clientes pueden usar esos JSON Schemas directamente, proporcionar mejores herramientas de generación de código, etc.

Los responses generadas en el OpenAPI para esta path operation serán:

{
    "responses": {
        "404": {
            "description": "Additional Response",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/Message"
                    }
                }
            }
        },
        "200": {
            "description": "Successful Response",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/Item"
                    }
                }
            }
        },
        "422": {
            "description": "Validation Error",
            "content": {
                "application/json": {
                    "schema": {
                        "$ref": "#/components/schemas/HTTPValidationError"
                    }
                }
            }
        }
    }
}

Los esquemas se referencian a otro lugar dentro del esquema de OpenAPI:

{
    "components": {
        "schemas": {
            "Message": {
                "title": "Message",
                "required": [
                    "message"
                ],
                "type": "object",
                "properties": {
                    "message": {
                        "title": "Message",
                        "type": "string"
                    }
                }
            },
            "Item": {
                "title": "Item",
                "required": [
                    "id",
                    "value"
                ],
                "type": "object",
                "properties": {
                    "id": {
                        "title": "Id",
                        "type": "string"
                    },
                    "value": {
                        "title": "Value",
                        "type": "string"
                    }
                }
            },
            "ValidationError": {
                "title": "ValidationError",
                "required": [
                    "loc",
                    "msg",
                    "type"
                ],
                "type": "object",
                "properties": {
                    "loc": {
                        "title": "Location",
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "msg": {
                        "title": "Message",
                        "type": "string"
                    },
                    "type": {
                        "title": "Error Type",
                        "type": "string"
                    }
                }
            },
            "HTTPValidationError": {
                "title": "HTTPValidationError",
                "type": "object",
                "properties": {
                    "detail": {
                        "title": "Detail",
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/ValidationError"
                        }
                    }
                }
            }
        }
    }
}

Media types adicionales para el response principal

Puedes usar este mismo parámetro responses para agregar diferentes media type para el mismo response principal.

Por ejemplo, puedes agregar un media type adicional de image/png, declarando que tu path operation puede devolver un objeto JSON (con media type application/json) o una imagen PNG:

from typing import Union

from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        200: {
            "content": {"image/png": {}},
            "description": "Return the JSON item or an image.",
        }
    },
)
async def read_item(item_id: str, img: Union[bool, None] = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Nota

Nota que debes devolver la imagen usando un FileResponse directamente.

Información

A menos que especifiques un media type diferente explícitamente en tu parámetro responses, FastAPI asumirá que el response tiene el mismo media type que la clase de response principal (por defecto application/json).

Pero si has especificado una clase de response personalizada con None como su media type, FastAPI usará application/json para cualquier response adicional que tenga un modelo asociado.

Combinando información

También puedes combinar información de response de múltiples lugares, incluyendo los parámetros response_model, status_code, y responses.

Puedes declarar un response_model, usando el código de estado predeterminado 200 (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en responses, directamente en el esquema de OpenAPI.

FastAPI manterá la información adicional de responses y la combinará con el JSON Schema de tu modelo.

Por ejemplo, puedes declarar un response con un código de estado 404 que usa un modelo Pydantic y tiene una description personalizada.

Y un response con un código de estado 200 que usa tu response_model, pero incluye un example personalizado:

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


class Message(BaseModel):
    message: str


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={
        404: {"model": Message, "description": "The item was not found"},
        200: {
            "description": "Item requested by ID",
            "content": {
                "application/json": {
                    "example": {"id": "bar", "value": "The bar tenders"}
                }
            },
        },
    },
)
async def read_item(item_id: str):
    if item_id == "foo":
        return {"id": "foo", "value": "there goes my hero"}
    else:
        return JSONResponse(status_code=404, content={"message": "Item not found"})

Todo se combinará e incluirá en tu OpenAPI, y se mostrará en la documentación de la API:

Combina responses predefinidos y personalizados

Es posible que desees tener algunos responses predefinidos que se apliquen a muchas path operations, pero que quieras combinarlos con responses personalizados necesarios por cada path operation.

Para esos casos, puedes usar la técnica de Python de "desempaquetar" un dict con **dict_to_unpack:

old_dict = {
    "old key": "old value",
    "second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}

Aquí, new_dict contendrá todos los pares clave-valor de old_dict más el nuevo par clave-valor:

{
    "old key": "old value",
    "second old key": "second old value",
    "new key": "new value",
}

Puedes usar esa técnica para reutilizar algunos responses predefinidos en tus path operations y combinarlos con otros personalizados adicionales.

Por ejemplo:

from typing import Union

from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel


class Item(BaseModel):
    id: str
    value: str


responses = {
    404: {"description": "Item not found"},
    302: {"description": "The item was moved"},
    403: {"description": "Not enough privileges"},
}


app = FastAPI()


@app.get(
    "/items/{item_id}",
    response_model=Item,
    responses={**responses, 200: {"content": {"image/png": {}}}},
)
async def read_item(item_id: str, img: Union[bool, None] = None):
    if img:
        return FileResponse("image.png", media_type="image/png")
    else:
        return {"id": "foo", "value": "there goes my hero"}

Más información sobre responses OpenAPI

Para ver exactamente qué puedes incluir en los responses, puedes revisar estas secciones en la especificación OpenAPI:

  • Objeto de Responses de OpenAPI, incluye el Response Object.
  • Objeto de Response de OpenAPI, puedes incluir cualquier cosa de esto directamente en cada response dentro de tu parámetro responses. Incluyendo description, headers, content (dentro de este es que declaras diferentes media types y JSON Schemas), y links.