O sistema de dependências do FastAPI permite declarar o que uma rota precisa sem repetir autenticação, paginação ou criação de sessão em cada função. Depends não é um contêiner mágico: normalmente recebe uma função ou objeto chamável e fornece seu resultado ao endpoint.

Se você está começando, veja primeiro como criar uma API REST com FastAPI.

Reutilizar parâmetros

from typing import Annotated
from fastapi import Depends, FastAPI, Query

app = FastAPI()

def paginacao(
    pagina: Annotated[int, Query(ge=1)] = 1,
    limite: Annotated[int, Query(ge=1, le=100)] = 20,
) -> tuple[int, int]:
    return pagina, limite

Paginacao = Annotated[tuple[int, int], Depends(paginacao)]

@app.get("/produtos")
def listar_produtos(valores: Paginacao):
    pagina, limite = valores
    return {"pagina": pagina, "limite": limite}

O alias com Annotated reduz repetição sem esconder o tipo recebido. A documentação oficial recomenda essa forma nas versões atuais.

Autenticação como subdependência

from fastapi import Header, HTTPException

def token_atual(authorization: Annotated[str | None, Header()] = None) -> str:
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Token ausente")
    return authorization.removeprefix("Bearer ")

def usuario_atual(token: Annotated[str, Depends(token_atual)]) -> dict:
    return validar_token(token)

Uma rota pode depender de usuario_atual; o FastAPI resolve token_atual antes. Por padrão, resultados repetidos são armazenados durante a requisição. Consulte subdependências e cache antes de usar use_cache=False.

Recursos com yield

Uma sessão de banco precisa ser fechada:

def obter_sessao():
    sessao = SessionLocal()
    try:
        yield sessao
    finally:
        sessao.close()

O trecho anterior ao yield prepara o recurso e finally garante limpeza. Evite transações implícitas difíceis de enxergar; defina claramente onde commit e rollback acontecem. O guia de SQLAlchemy ajuda a estruturar a camada de dados.

Montar uma cadeia de autenticação

Separe extração da credencial, validação e autorização:

from fastapi import Header, HTTPException

def token_bearer(authorization: Annotated[str | None, Header()] = None) -> str:
    if authorization is None or not authorization.startswith("Bearer "):
        raise HTTPException(
            status_code=401,
            detail="Token ausente",
            headers={"WWW-Authenticate": "Bearer"},
        )
    return authorization.removeprefix("Bearer ")

def usuario_atual(token: Annotated[str, Depends(token_bearer)]) -> Usuario:
    usuario = servico_token.validar(token)
    if usuario is None:
        raise HTTPException(status_code=401, detail="Token inválido")
    return usuario

def admin_ativo(usuario: Annotated[Usuario, Depends(usuario_atual)]) -> Usuario:
    if not usuario.ativo or "admin" not in usuario.papeis:
        raise HTTPException(status_code=403, detail="Permissão insuficiente")
    return usuario

Credencial inválida gera 401; usuário autenticado sem permissão gera 403. Não receba token pela URL, pois ela aparece em logs. A verificação real deve validar assinatura, algoritmo, expiração, emissor e audiência quando aplicável.

Configurar com classes chamáveis

class ExigirPapel:
    def __init__(self, papel: str):
        self.papel = papel

    def __call__(self, usuario: Annotated[Usuario, Depends(usuario_atual)]):
        if self.papel not in usuario.papeis:
            raise HTTPException(status_code=403, detail="Permissão insuficiente")
        return usuario

exigir_editor = ExigirPapel("editor")

Qualquer objeto chamável pode ser dependência. Crie configuração na inicialização e não guarde estado mutável da requisição em instâncias compartilhadas.

Tratar yield como contexto

def obter_sessao():
    with SessionLocal() as sessao:
        try:
            yield sessao
            sessao.commit()
        except Exception:
            sessao.rollback()
            raise

Commit aqui é escolha arquitetural. Pode ser conveniente, mas esconde limites de transação. Muitas equipes deixam a dependência apenas abrir e fechar e fazem commit no serviço. Documente a convenção e teste rollback. O guia oficial de yield detalha a limpeza.

Use async def ao aguardar driver assíncrono. Colocar trabalho bloqueante dentro de async def não o torna não bloqueante.

Compreender o cache por requisição

Uma dependência repetida no grafo normalmente executa uma vez e reutiliza o valor naquela requisição. Isso é útil para usuário e sessão, mas não é cache global.

def nonce_requisicao() -> str:
    return secrets.token_urlsafe(16)

Nonce = Annotated[str, Depends(nonce_requisicao, use_cache=False)]

Use use_cache=False somente quando cada declaração exigir valor novo. Cache entre requisições precisa de expiração e invalidação explícitas.

Aplicar dependência sem receber valor

def exigir_chave(x_api_key: Annotated[str, Header()]) -> None:
    if not secrets.compare_digest(x_api_key, settings.chave_interna):
        raise HTTPException(status_code=401, detail="Chave inválida")

@app.get("/interno/saude", dependencies=[Depends(exigir_chave)])
def saude_interna():
    return {"status": "ok"}

Dependências no router protegem um conjunto. Dependências globais afetam tudo, inclusive documentação e health checks no escopo. Efeitos de auditoria devem ser explícitos e resilientes.

Substituir em testes

def sessao_falsa():
    yield FakeSession()

app.dependency_overrides[obter_sessao] = sessao_falsa
try:
    resposta = client.get("/produtos")
    assert resposta.status_code == 200
finally:
    app.dependency_overrides.clear()

Overrides permitem testar a rota sem banco ou serviço externo, mas testes de integração ainda são necessários. Limpar o mapa é indispensável porque o app costuma sobreviver entre testes. Uma fixture pode instalar e remover o override.

Evite retornar um contêiner enorme para rotas buscarem serviços arbitrários. Isso esconde acoplamento. Dependências são adequadas para credenciais, configuração, sessões e construção leve de serviços; decisões de domínio ficam em objetos testáveis sem FastAPI. Revise dependências que fazem chamadas repetidas, mudam estado global ou capturam exceções amplas. Confirme também os parâmetros gerados no OpenAPI e nunca exponha segredos em exemplos.

Depurar o grafo

Quando uma rota responde 422, identifique qual parâmetro de dependência não foi validado. Um valor declarado por engano como query em vez de header impede a rota de executar. Use anotações explícitas e confira no OpenAPI a localização e as restrições.

Se um trabalho se repete, observe a identidade dos callables. Dois wrappers com a mesma lógica continuam sendo dependências diferentes e não compartilham resultado. Se dados atravessam usuários, lembre que o cache interno dura somente uma requisição; investigue objetos globais, cache da aplicação ou serviço mutável.

Instrumente duração nas fronteiras mais lentas, sem registrar token ou informação pessoal. Uma dependência universal que chama serviço remoto adiciona latência a todas as rotas e amplia a indisponibilidade. Divida o grafo conforme a necessidade real.

Definir responsabilidades

Uma dependência de configuração pode retornar objeto imutável carregado na inicialização. Uma dependência de sessão fornece recurso daquele request. Aprovar pedido, calcular desconto ou decidir elegibilidade são regras do domínio e devem ser testáveis sem o framework.

Não capture toda Exception para responder mensagem genérica. Garanta limpeza com finally, registre falhas operacionais de modo seguro e traduza erros conhecidos na fronteira HTTP. Converter tudo em 401 ou 500 faz clientes reagirem errado e dificulta diagnóstico.

Mantenha dependências pequenas. Se uma função valida token, busca usuário, abre transação, consulta permissões e registra auditoria, mudanças sem relação passam a afetar o mesmo componente. Separe etapas e componha o grafo.

Testar os casos reveladores

Cubra entrada ausente, credencial malformada, token expirado, usuário inativo, permissão insuficiente, sessão fechada após exceção e duas requisições independentes. Verifique WWW-Authenticate quando apropriado. Esses cenários demonstram isolamento.

Nos testes de rota, substitua serviços lentos por implementações controladas. Na integração, execute o wiring real e confirme transação e validação de token. Quando yield faz parte do contrato, a substituta também deve representar aquisição e limpeza.

Evite override global instalado no import do módulo de testes. Use fixture com try e finally, pois uma asserção interrompida não pode contaminar o cenário seguinte. Se vários apps são criados, aplique o override exatamente na instância usada pelo cliente.

Escolher o alcance correto

Uma regra comum pode ser declarada no APIRouter. Isso torna o limite de segurança visível. Porém, uma dependência colocada somente em dependencies=[...] tem seu valor descartado; quando a rota precisa do usuário, receba-o como parâmetro tipado.

Dependências globais devem ser raras. Uma verificação imposta a toda aplicação pode bloquear documentação, health check e endpoints públicos. Faça inventário das rotas antes de ampliar o escopo e preserve caminhos de monitoramento compatíveis com a política de segurança.

Subdependências profundas também merecem revisão. Um grafo longo pode ser correto, mas se a ordem e os efeitos não são claros, extraia serviços explícitos. A injeção deve tornar requisitos visíveis, não criar execução surpreendente.

Revisar contrato e operação

Leia a documentação gerada como consumidor. Headers obrigatórios, códigos de erro, modelos e descrições precisam corresponder ao comportamento. Um header originado em dependência aparece no OpenAPI, então nomes e validações devem ser estáveis.

Defina timeout para chamadas externas e política para indisponibilidade. Autenticação normalmente deve falhar fechada; telemetria opcional pode falhar sem interromper a operação principal. Essa escolha precisa ser consciente.

Durante revisão, procure estado compartilhado, cliente criado por request sem necessidade, sessão não encerrada e mensagens que revelam detalhes. Tipos de retorno explícitos ajudam editor e manutenção. Nomes como usuario_atual comunicam o valor fornecido melhor que verificar.

Injeção de dependências bem aplicada não elimina acoplamento; ela o torna explícito e substituível. O resultado desejado é uma rota legível, uma regra de domínio independente e recursos encerrados corretamente em sucesso ou falha.