Ir para o conteúdo

Retornos Adicionais no OpenAPI

Aviso

Este é um tema bem avançado.

Se você está começando com o FastAPI, provavelmente você não precisa disso.

Você pode declarar retornos adicionais, com códigos de status adicionais, media types, descrições, etc.

Essas respostas adicionais serão incluídas no esquema do OpenAPI, e também aparecerão na documentação da API.

Porém para as respostas adicionais, você deve garantir que está retornando um Response como por exemplo o JSONResponse diretamente, junto com o código de status e o conteúdo.

Retorno Adicional com model

Você pode fornecer o parâmetro responses aos seus decoradores de caminho.

Este parâmetro recebe um dict, as chaves são os códigos de status para cada retorno, como por exemplo 200, e os valores são um outro dict com a informação de cada um deles.

Cada um desses dict de retorno pode ter uma chave model, contendo um modelo do Pydantic, assim como o response_model.

O FastAPI pegará este modelo, gerará o esquema JSON dele e incluirá no local correto do OpenAPI.

Por exemplo, para declarar um outro retorno com o status code 404 e um modelo do Pydantic chamado Message, você pode escrever:

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

Lembre-se que você deve retornar o JSONResponse diretamente.

Informação

A chave model não é parte do OpenAPI.

O FastAPI pegará o modelo do Pydantic, gerará o JSON Schema, e adicionará no local correto.

O local correto é:

  • Na chave content, que tem como valor um outro objeto JSON (dict) que contém:
    • Uma chave com o media type, como por exemplo application/json, que contém como valor um outro objeto JSON, contendo::
      • Uma chave schema, que contém como valor o JSON Schema do modelo, sendo este o local correto.
        • O FastAPI adiciona aqui a referência dos esquemas JSON globais que estão localizados em outro lugar, ao invés de incluí-lo diretamente. Deste modo, outras aplicações e clientes podem utilizar estes esquemas JSON diretamente, fornecer melhores ferramentas de geração de código, etc.

O retorno gerado no OpenAI para esta operação de caminho será:

{
    "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"
                    }
                }
            }
        }
    }
}

Os esquemas são referenciados em outro local dentro do esquema 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 adicionais para o retorno principal

Você pode utilizar o mesmo parâmetro responses para adicionar diferentes media types para o mesmo retorno principal.

Por exemplo, você pode adicionar um media type adicional de image/png, declarando que a sua operação de caminho pode retornar um objeto JSON (com o media type application/json) ou uma imagem 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

Note que você deve retornar a imagem utilizando um FileResponse diretamente.

Informação

A menos que você especifique um media type diferente explicitamente em seu parâmetro responses, o FastAPI assumirá que o retorno possui o mesmo media type contido na classe principal de retorno (padrão application/json).

Porém se você especificou uma classe de retorno com o valor None como media type, o FastAPI utilizará application/json para qualquer retorno adicional que possui um modelo associado.

Combinando informações

Você também pode combinar informações de diferentes lugares, incluindo os parâmetros response_model, status_code, e responses.

Você pode declarar um response_model, utilizando o código de status padrão 200 (ou um customizado caso você precise), e depois adicionar informações adicionais para esse mesmo retorno em responses, diretamente no esquema OpenAPI.

O FastAPI manterá as informações adicionais do responses, e combinará com o esquema JSON do seu modelo.

Por exemplo, você pode declarar um retorno com o código de status 404 que utiliza um modelo do Pydantic que possui um description customizado.

E um retorno com o código de status 200 que utiliza o seu response_model, porém inclui um example customizado:

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"})

Isso será combinado e incluído em seu OpenAPI, e disponibilizado na documentação da sua API:

Combinar retornos predefinidos e personalizados

Você pode querer possuir alguns retornos predefinidos que são aplicados para diversas operações de caminho, porém você deseja combinar com retornos personalizados que são necessários para cada operação de caminho.

Para estes casos, você pode utilizar a técnica do Python de "desempacotamento" de um dict utilizando **dict_to_unpack:

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

Aqui, o new_dict terá todos os pares de chave-valor do old_dict mais o novo par de chave-valor:

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

Você pode utilizar essa técnica para reutilizar alguns retornos predefinidos nas suas operações de caminho e combiná-las com personalizações adicionais.

Por exemplo:

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"}

Mais informações sobre retornos OpenAPI

Para verificar exatamente o que você pode incluir nos retornos, você pode conferir estas seções na especificação do OpenAPI:

  • Objeto de Retorno OpenAPI, inclui o Response Object.
  • Objeto de Retorno OpenAPI, você pode incluir qualquer coisa dele diretamente em cada retorno dentro do seu parâmetro responses. Incluindo description, headers, content (dentro dele que você declara diferentes media types e esquemas JSON), e links.