Pular para o conteúdo

    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:

    intermediario/cap13_dependencias.pylinhas 10 a 40
    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)
    
    Saída
    {'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:

    intermediario/cap13_dependencias.pylinhas 45 a 63
    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())
    
    Saída
    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

    1. A requisição chega.
    2. O FastAPI valida os parâmetros e resolve as dependências, na ordem.
    3. Se uma dependência levantar uma exceção (como o 401), a rota nem roda.
    4. 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:

    intermediario/cap13_dependencias.pylinhas 68 a 89
    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())
    
    Saída
    {'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:

    intermediario/cap13_dependencias.pylinhas 94 a 112
    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)
    
    Saída
    ['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:

    intermediario/cap13_dependencias.pylinhas 117 a 134
    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())
    
    Saída
    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:

    intermediario/cap13_dependencias.pylinhas 139 a 150
    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())
    
    Saída
    {'q': 'caneta', 'ativo': False}
    
    RecursoUso
    Depends(funcao)Reaproveitar lógica e validação entre rotas
    Dependência com yieldAbrir e fechar recursos (sessão do banco)
    dependencies=[...]Exigir algo (autenticação) sem precisar do valor
    Dependência de dependênciaMontar camadas (conexão, repositório, serviço)
    app.dependency_overridesTrocar 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.