Configuração Avançada da Operação de Rota¶
operationId do OpenAPI¶
Aviso
Se você não é um "especialista" no OpenAPI, você provavelmente não precisa disso.
Você pode definir o operationId
do OpenAPI que será utilizado na sua operação de rota com o parâmetro operation_id
.
Você precisa ter certeza que ele é único para cada operação.
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", operation_id="some_specific_id_you_define")
async def read_items():
return [{"item_id": "Foo"}]
Utilizando o nome da função de operação de rota como o operationId¶
Se você quiser utilizar o nome das funções da sua API como operationId
s, você pode iterar sobre todos esses nomes e sobrescrever o operationId
em cada operação de rota utilizando o APIRoute.name
dela.
Você deve fazer isso depois de adicionar todas as suas operações de rota.
from fastapi import FastAPI
from fastapi.routing import APIRoute
app = FastAPI()
@app.get("/items/")
async def read_items():
return [{"item_id": "Foo"}]
def use_route_names_as_operation_ids(app: FastAPI) -> None:
"""
Simplify operation IDs so that generated API clients have simpler function
names.
Should be called only after all routes have been added.
"""
for route in app.routes:
if isinstance(route, APIRoute):
route.operation_id = route.name # in this case, 'read_items'
use_route_names_as_operation_ids(app)
Dica
Se você chamar app.openapi()
manualmente, os operationId
s devem ser atualizados antes dessa chamada.
Aviso
Se você fizer isso, você tem que ter certeza de que cada uma das suas funções de operação de rota tem um nome único.
Mesmo que elas estejam em módulos (arquivos Python) diferentes.
Excluir do OpenAPI¶
Para excluir uma operação de rota do esquema OpenAPI gerado (e por consequência, dos sistemas de documentação automáticos), utilize o parâmetro include_in_schema
e defina ele como False
:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", include_in_schema=False)
async def read_items():
return [{"item_id": "Foo"}]
Descrição avançada a partir de docstring¶
Você pode limitar as linhas utilizadas a partir de uma docstring de uma função de operação de rota para o OpenAPI.
Adicionar um \f
(um caractere de escape para alimentação de formulário) faz com que o FastAPI restrinja a saída utilizada pelo OpenAPI até esse ponto.
Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto.
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
@app.post("/items/", response_model=Item, summary="Create an item")
async def create_item(item: Item):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
\f
:param item: User input.
"""
return item
Respostas Adicionais¶
Você provavelmente já viu como declarar o response_model
e status_code
para uma operação de rota.
Isso define os metadados sobre a resposta principal da operação de rota.
Você também pode declarar respostas adicionais, com seus modelos, códigos de status, etc.
Existe um capítulo inteiro da nossa documentação sobre isso, você pode ler em Retornos Adicionais no OpenAPI.
Extras do OpenAPI¶
Quando você declara uma operação de rota na sua aplicação, o FastAPI irá gerar os metadados relevantes da operação de rota automaticamente para serem incluídos no esquema do OpenAPI.
Nota
Na especificação do OpenAPI, isso é chamado de um Objeto de Operação.
Ele possui toda a informação sobre a operação de rota e é usado para gerar a documentação automaticamente.
Ele inclui os atributos tags
, parameters
, requestBody
, responses
, etc.
Esse esquema específico para uma operação de rota normalmente é gerado automaticamente pelo FastAPI, mas você também pode estender ele.
Dica
Esse é um ponto de extensão de baixo nível.
Caso você só precise declarar respostas adicionais, uma forma conveniente de fazer isso é com Retornos Adicionais no OpenAPI.
Você pode estender o esquema do OpenAPI para uma operação de rota utilizando o parâmetro openapi_extra
.
Extensões do OpenAPI¶
Esse parâmetro openapi_extra
pode ser útil, por exemplo, para declarar Extensões do OpenAPI:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"})
async def read_items():
return [{"item_id": "portal-gun"}]
Se você abrir os documentos criados automaticamente para a API, sua extensão aparecerá no final da operação de rota específica.
E se você olhar o esquema OpenAPI resultante (na rota /openapi.json
da sua API), você verá que a sua extensão também faz parte da operação de rota específica:
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"summary": "Read Items",
"operationId": "read_items_items__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"x-aperture-labs-portal": "blue"
}
}
}
}
Esquema de operação de rota do OpenAPI personalizado¶
O dicionário em openapi_extra
vai ter todos os seus níveis mesclados dentro do esquema OpenAPI gerado automaticamente para a operação de rota.
Então, você pode adicionar dados extras para o esquema gerado automaticamente.
Por exemplo, você poderia optar por ler e validar a requisição com seu próprio código, sem utilizar funcionalidades automatizadas do FastAPI com o Pydantic, mas você ainda pode quere definir a requisição no esquema OpenAPI.
Você pode fazer isso com openapi_extra
:
from fastapi import FastAPI, Request
app = FastAPI()
def magic_data_reader(raw_body: bytes):
return {
"size": len(raw_body),
"content": {
"name": "Maaaagic",
"price": 42,
"description": "Just kiddin', no magic here. ✨",
},
}
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {
"application/json": {
"schema": {
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"},
},
}
}
},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
data = magic_data_reader(raw_body)
return data
Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo analisado como JSON, ele é lido diretamente como bytes
e a função magic_data_reader()
seria a responsável por analisar ele de alguma forma.
De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição.
Tipo de conteúdo do OpenAPI personalizado¶
Utilizando esse mesmo truque, você pode utilizar um modelo Pydantic para definir o esquema JSON que é então incluído na seção do esquema personalizado do OpenAPI na operação de rota.
E você pode fazer isso até mesmo quando os dados da requisição não seguem o formato JSON.
Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o esquema JSON dos modelos Pydantic nem a validação automática do JSON. Na verdade, estamos declarando o tipo do conteúdo da requisição como YAML, em vez de JSON:
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.model_validate(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors(include_url=False))
return item
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.parse_obj(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
return item
Informação
Na versão 1 do Pydantic, o método para obter o esquema JSON de um modelo é Item.schema()
, na versão 2 do Pydantic, o método é Item.model_json_schema()
Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um esquema JSON manualmente para os dados que queremos receber no formato YAML.
Então utilizamos a requisição diretamente, e extraímos o corpo como bytes
. Isso significa que o FastAPI não vai sequer tentar analisar o corpo da requisição como JSON.
E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estamos utilizando o mesmo modelo Pydantic para validar o conteúdo YAML:
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.model_json_schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.model_validate(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors(include_url=False))
return item
from typing import List
import yaml
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, ValidationError
app = FastAPI()
class Item(BaseModel):
name: str
tags: List[str]
@app.post(
"/items/",
openapi_extra={
"requestBody": {
"content": {"application/x-yaml": {"schema": Item.schema()}},
"required": True,
},
},
)
async def create_item(request: Request):
raw_body = await request.body()
try:
data = yaml.safe_load(raw_body)
except yaml.YAMLError:
raise HTTPException(status_code=422, detail="Invalid YAML")
try:
item = Item.parse_obj(data)
except ValidationError as e:
raise HTTPException(status_code=422, detail=e.errors())
return item
Informação
Na versão 1 do Pydantic, o método para analisar e validar um objeto era Item.parse_obj()
, na versão 2 do Pydantic, o método é chamado de Item.model_validate()
.
Dica
Aqui reutilizamos o mesmo modelo do Pydantic.
Mas da mesma forma, nós poderíamos ter validado de alguma outra forma.