Capítulo 5, Básico
Parâmetros de consulta
O que vem depois do `?` na URL: filtros, busca, ordenação, paginação. Ajustam **como** você quer ver os dados, e não **quais** dados são o recurso.
Parâmetros obrigatórios e opcionais
Qualquer parâmetro da função que não faz parte do caminho vira um parâmetro de consulta. Sem valor padrão, ele é obrigatório. Com valor padrão, é opcional:
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/busca")
def buscar(q: str, limite: int = 10, ordem: str | None = None):
return {"q": q, "limite": limite, "ordem": ordem}
cliente = TestClient(app)
print(cliente.get("/busca?q=caneta").json())
print(cliente.get("/busca?q=caneta&limite=50&ordem=preco").json())
{'q': 'caneta', 'limite': 10, 'ordem': None}
{'q': 'caneta', 'limite': 50, 'ordem': 'preco'}
O limite assumiu o padrão 10 quando não veio. A ordem ficou None (JSON null). Já o q não tem padrão, e esquecê-lo é um erro:
resposta = cliente.get("/busca")
erro = resposta.json()["detail"][0]
print(resposta.status_code, erro["loc"], erro["type"])
422 ['query', 'q'] missing
Opcional: `str | None = None`, e não `str = None`
Muito material escreve nome: str = None. Funciona, mas mente sobre o tipo: a função diz que recebe um str e dá None como padrão. O verificador de tipos reclama, e o documento OpenAPI perde a informação de que o valor pode ser nulo:
@app.get("/a")
def com_none_simples(nome: str = None):
return nome
@app.get("/b")
def com_uniao(nome: str | None = None):
return nome
caminhos = app.openapi()["paths"]
print(caminhos["/a"]["get"]["parameters"][0]["schema"])
print(caminhos["/b"]["get"]["parameters"][0]["schema"])
{'type': 'string', 'title': 'Nome'}
{'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Nome'}
A segunda forma descreve a verdade (string ou null), e é a que eu uso sempre.
Booleanos e listas
Um bool aceita true, false, 1, 0, yes, no, on e off. Para receber vários valores do mesmo parâmetro, declare uma lista com Query:
from typing import Annotated
from fastapi import Query
@app.get("/produtos")
def produtos(ativo: bool = True, etiquetas: Annotated[list[str] | None, Query()] = None):
return {"ativo": ativo, "etiquetas": etiquetas}
print(cliente.get("/produtos?ativo=no").json())
print(cliente.get("/produtos?etiquetas=novo&etiquetas=oferta").json())
{'ativo': False, 'etiquetas': None}
{'ativo': True, 'etiquetas': ['novo', 'oferta']}
Validação com `Query`
O Query impõe tamanho mínimo e máximo em textos e limites em números, e as regras vão para a documentação:
@app.get("/pesquisa")
def pesquisa(
termo: Annotated[str, Query(min_length=3, max_length=20)],
pagina: Annotated[int, Query(ge=1)] = 1,
por_pagina: Annotated[int, Query(ge=1, le=100)] = 20,
):
return {"termo": termo, "pagina": pagina, "por_pagina": por_pagina}
print(cliente.get("/pesquisa?termo=caneta").json())
resposta = cliente.get("/pesquisa?termo=ab&por_pagina=500")
print(resposta.status_code, [e["loc"][-1] for e in resposta.json()["detail"]])
{'termo': 'caneta', 'pagina': 1, 'por_pagina': 20}
422 ['termo', 'por_pagina']
Os dois erros voltaram juntos: o termo curto demais e o por_pagina acima de 100. O FastAPI valida tudo e informa tudo de uma vez, em vez de parar no primeiro problema.
| Parâmetro | Vai em | Obrigatório? |
|---|---|---|
/usuarios/{id} | Caminho | Sempre |
?q=abc sem padrão | Consulta | Sim |
?q=abc com padrão ou None | Consulta | Não |
| Objeto Pydantic | Corpo (próximo capítulo) | Conforme o modelo |
Exercício 1
Uma busca com limites
Crie GET /livros com os parâmetros autor (opcional), ano_minimo (inteiro, padrão 1900, mínimo 1000) e limite (inteiro, padrão 10, entre 1 e 50). Confira os padrões e um valor inválido.