Capítulo 13, Intermediário
Injeção de dependências
Uma dependência é uma função que a sua rota **pede**, em vez de chamar. O FastAPI executa, resolve e entrega o resultado, e é com isso que você reaproveita autenticação, paginação e acesso ao banco sem copiar código.
A ideia
Em vez de cada rota repetir "ler o token, conferir o usuário, abrir a conexão", você escreve isso uma vez, como uma função, e declara no parâmetro: Depends(a_funcao). O FastAPI chama a função antes da sua rota e injeta o valor devolvido. Um primeiro exemplo clássico é a paginação, que várias rotas repetem:
from typing import Annotated
from fastapi import Depends, FastAPI, Query
from fastapi.testclient import TestClient
app = FastAPI()
def paginacao(
pagina: Annotated[int, Query(ge=1)] = 1,
limite: Annotated[int, Query(ge=1, le=100)] = 10,
) -> dict:
return {"pagina": pagina, "limite": limite}
Paginacao = Annotated[dict, Depends(paginacao)]
@app.get("/produtos")
def produtos(p: Paginacao):
return {"recurso": "produtos", **p}
@app.get("/clientes")
def clientes(p: Paginacao):
return {"recurso": "clientes", **p}
cliente = TestClient(app)
print(cliente.get("/produtos?pagina=2").json())
print(cliente.get("/clientes?limite=500").status_code)
{'recurso': 'produtos', 'pagina': 2, 'limite': 10}
422
Os parâmetros pagina e limite e as suas regras agora existem uma vez, e valem para as duas rotas, inclusive na documentação. O alias Paginacao = Annotated[dict, Depends(paginacao)] deixa a assinatura das rotas curta.
Verificar um token
O caso mais comum é a autenticação. O token chega em um cabeçalho (nunca na URL, que fica em logs). O FastAPI converte o nome do parâmetro: x_token lê o cabeçalho x-token:
import secrets
from fastapi import Header, HTTPException
def verificar_token(x_token: Annotated[str | None, Header()] = None) -> str:
if x_token is None or not secrets.compare_digest(x_token, "segredo"):
raise HTTPException(status_code=401, detail="Não autorizado")
return "usuário autorizado"
@app.get("/dados-secretos")
def dados_secretos(quem: Annotated[str, Depends(verificar_token)]):
return {"mensagem": "dados secretos", "para": quem}
print(cliente.get("/dados-secretos").status_code)
print(cliente.get("/dados-secretos", headers={"x-token": "errado"}).status_code)
print(cliente.get("/dados-secretos", headers={"x-token": "segredo"}).json())
401
401
{'mensagem': 'dados secretos', 'para': 'usuário autorizado'}
A comparação usa secrets.compare_digest, que leva o mesmo tempo para qualquer entrada, e assim não permite descobrir o segredo medindo o tempo da resposta. Este token fixo é só para ensinar o mecanismo: o capítulo 20 faz isso de verdade, com JWT.
O fluxo
- A requisição chega.
- O FastAPI valida os parâmetros e resolve as dependências, na ordem.
- Se uma dependência levantar uma exceção (como o
401), a rota nem roda. - Se todas passarem, a rota roda com os valores injetados.
Dependências que dependem de outras
Uma dependência pode pedir outras. O FastAPI monta a cadeia e executa cada função uma vez por requisição, mesmo que várias a peçam:
chamadas = []
def conexao() -> str:
chamadas.append("conexao")
return "conexão aberta"
def repositorio(c: Annotated[str, Depends(conexao)]) -> str:
return f"repositório usando {c}"
def servico(c: Annotated[str, Depends(conexao)], r: Annotated[str, Depends(repositorio)]) -> str:
return f"serviço com {r}"
@app.get("/cadeia")
def cadeia(s: Annotated[str, Depends(servico)]):
return {"resultado": s, "vezes_que_a_conexao_rodou": len(chamadas)}
print(cliente.get("/cadeia").json())
{'resultado': 'serviço com repositório usando conexão aberta', 'vezes_que_a_conexao_rodou': 1}
A conexao foi pedida pelo repositorio e pelo servico, e rodou uma vez. É esse cache por requisição que permite compartilhar, por exemplo, a mesma sessão de banco entre várias dependências.
`yield`: abrir e fechar um recurso
Uma dependência com yield abre o recurso antes da rota e fecha depois, mesmo se ocorrer um erro. É o padrão para sessões de banco e conexões:
eventos = []
def recurso():
eventos.append("abre")
try:
yield "recurso"
finally:
eventos.append("fecha")
@app.get("/com-recurso")
def com_recurso(r: Annotated[str, Depends(recurso)]):
eventos.append("usa")
return {"recurso": r}
cliente.get("/com-recurso")
print(eventos)
['abre', 'usa', 'fecha']
Dependências sem usar o valor
Quando só importa a verificação (e não o valor), declare a dependência no decorador. Em um APIRouter, ela vale para todas as rotas do grupo:
from fastapi import APIRouter
admin = APIRouter(prefix="/admin", dependencies=[Depends(verificar_token)])
@admin.get("/usuarios")
def listar_usuarios():
return ["ana", "bia"]
@admin.get("/relatorio")
def relatorio():
return {"vendas": 10}
app.include_router(admin)
print(cliente.get("/admin/usuarios").status_code, cliente.get("/admin/relatorio").status_code)
print(cliente.get("/admin/usuarios", headers={"x-token": "segredo"}).json())
401 401
['ana', 'bia']
Uma classe como dependência
Quando os parâmetros se agrupam, uma classe organiza melhor. O Depends() sem argumento usa a própria classe:
class Filtros:
def __init__(self, q: str | None = None, ativo: bool = True):
self.q = q
self.ativo = ativo
@app.get("/busca")
def busca(f: Annotated[Filtros, Depends()]):
return {"q": f.q, "ativo": f.ativo}
print(cliente.get("/busca?q=caneta&ativo=false").json())
{'q': 'caneta', 'ativo': False}
| Recurso | Uso |
|---|---|
Depends(funcao) | Reaproveitar lógica e validação entre rotas |
Dependência com yield | Abrir e fechar recursos (sessão do banco) |
dependencies=[...] | Exigir algo (autenticação) sem precisar do valor |
| Dependência de dependência | Montar camadas (conexão, repositório, serviço) |
app.dependency_overrides | Trocar uma dependência nos testes (capítulo 24) |
Exercício 1
Exigir o papel de administrador
Crie a dependência somente_admin, que leia o cabeçalho x-papel e levante 403 se ele não for "admin". Use-a em GET /painel. Confira que a ausência do cabeçalho e um papel errado dão 403, e que admin dá 200.