Skip to content

    Chapter 13, Intermediate

    Dependency injection

    A dependency is a function that your route **requests**, instead of calling. FastAPI executes, resolves, and delivers the result, and this is how you reuse authentication, pagination, and database access without copying code.

    The idea

    Instead of each route repeating "read the token, check the user, open the connection", you write this once, as a function, and declare in the parameter: Depends(a_funcao). FastAPI calls the function before from your route and injects the returned value. A classic first example is pagination, which several routes repeat:

    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)
    
    Output
    {'recurso': 'produtos', 'pagina': 2, 'limite': 10}
    422
    

    The parameters pagina and limite and their rules now exist once, and apply to both routes, including in the documentation. The alias Paginacao = Annotated[dict, Depends(paginacao)] keeps the route signatures short.

    Verify a token

    The most common case is authentication. The token arrives in a header (never in the URL, which goes into logs). FastAPI converts the parameter name: x_token reads the header 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())
    
    Output
    401
    401
    {'mensagem': 'dados secretos', 'para': 'usuário autorizado'}
    

    The comparison uses secrets.compare_digest, which takes the same time for any input, thus not allowing the secret to be discovered by measuring the response time. This fixed token is just to teach the mechanism: chapter 20 does this for real, with JWT.

    The flow

    1. The request arrives.
    2. FastAPI validates the parameters and resolves the dependencies, in order.
    3. If a dependency raises an exception (like the 401), the route doesn't run.
    4. If all pass, the route runs with the injected values.

    Dependencies that depend on others

    A dependency can request others. FastAPI builds the chain and executes each function once per request, even if several request it:

    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())
    
    Output
    {'resultado': 'serviço com repositório usando conexão aberta', 'vezes_que_a_conexao_rodou': 1}
    

    The conexao was requested by the repositorio and by servico, and ran once. It is this request cache that allows sharing, for example, the same database session among several dependencies.

    `yield`: open and close a resource

    A dependency with yield opens the resource before the route and closes after, even if an error occurs. It is the standard for database sessions and connections:

    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)
    
    Output
    ['abre', 'usa', 'fecha']
    

    Dependencies without using the value

    When only the check matters (and not the value), declare the dependency in the decorator. In a APIRouter, it applies to all routes in the group:

    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())
    
    Output
    401 401
    ['ana', 'bia']
    

    A class as a dependency

    When parameters are grouped, a class organizes better. The Depends() without arguments uses the class itself:

    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())
    
    Output
    {'q': 'caneta', 'ativo': False}
    
    ResourceUsage
    Depends(funcao)Reuse logic and validation between routes
    Dependency with yieldOpen and close resources (database session)
    dependencies=[...]Require something (authentication) without needing the value
    Dependency of dependencySet up layers (connection, repository, service)
    app.dependency_overridesChange a dependency in tests (chapter 24)

    Exercise 1

    Require the admin role

    Create the dependency somente_admin, which reads the header x-papel and raises 403 if it is not "admin". Use it in GET /painel. Check that the absence of the header and a wrong role give 403, and that admin gives 200.