Sistema de Injeção de Dependência — O Design Central do FastAPI
Injeção de dependência é como as interfaces de blocos de LEGO—cada bloco se preocupa apenas com o que deve fornecer e não precisa saber quem está usando. Conexões de banco de dados, o usuário atual e verificações de permissão são resolvidos automaticamente através de camadas aninhadas.
1. O Que Você Vai Aprender
- Noções básicas de
Depends(): Dependências de Função, Dependências de Classe, Subdependências Aninhadas - Ciclo de Vida da Dependência: Nível de Requisição vs. Nível de Aplicação (dependências
yielde Limpeza de Recursos) - Dependências Globais e Dependências de Grupo de Rotas:
dependencies=[Depends(...)] - Substituição de Dependências e Testes: Dicas Práticas com
app.dependency_overrides - Cenário da Alice: Dependência de sessão DB do PriceTracker, dependência de usuário atual e dependência de verificação de permissão—uma cadeia DI aninhada de três camadas
2. A História Real da Alice
(1) Problema: Cada endpoint repete a obtenção de dados do banco de informações do usuário
O PriceTracker da Alice tem 20 endpoints, cada um dos quais requer abrir uma conexão de banco de dados, verificar um token JWT, obter o usuário atual e verificar permissões. Ela copia e cola as mesmas 15 linhas de código de inicialização em cada função de endpoint, então se o método de conexão com o banco de dados mudar, ela precisa fazer 20 modificações separadas.
(2) Soluções com Injeção de Dependência
A injeção de dependência do FastAPI extrai lógica repetitiva em funções de dependência reutilizáveis. Endpoints só precisam declarar db = Depends(get_db), e o FastAPI resolve e injeta automaticamente, incluindo resolução recursiva de subdependências.
from fastapi import Depends
async def get_db():
db = Database()
yield db
db.close()
async def get_current_user(token: str, db=Depends(get_db)):
return verify_token(token, db)
@app.get("/products")
async def list_products(user=Depends(get_current_user), db=Depends(get_db)):
# user e db são injetados automaticamente
...
(3) Resultado
Conexões de banco de dados, autenticação de usuário e verificações de permissão—que antes exigiam 15 linhas de código para cada um dos 20 endpoints—foram consolidadas em três funções de dependência, então uma mudança feita em um lugar entra em vigor globalmente. Durante os testes, basta usar app.dependency_overrides[get_db] = lambda: mock_db para substituir a informação do banco de dados de todos os endpoints com uma única linha de código.
3. Noções Básicas do Depends()
(1) Dependências Funcionais
(1) ▶ Exemplo: Dependências de Função Simples
from fastapi import FastAPI, Depends
app = FastAPI()
def common_parameters(
q: str | None = None,
skip: int = 0,
limit: int = 100,
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/products")
async def list_products(commons: dict = Depends(common_parameters)):
return commons
@app.get("/prices")
async def list_prices(commons: dict = Depends(common_parameters)):
return commons
Saída:
# Função definida com sucesso
(2) ▶ Exemplo: Dependências de Classe
from fastapi import FastAPI, Depends, Query
class CommonQueryParams:
def __init__(
self,
q: str | None = Query(None),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=200),
):
self.q = q
self.skip = skip
self.limit = limit
app = FastAPI()
@app.get("/products")
async def list_products(commons: CommonQueryParams = Depends(CommonQueryParams)):
return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}
Saída:
# Função definida com sucesso
(2) Dependências de Função vs. Dependências de Classe
| Dimensão | Dependência de Função | Dependência de Classe |
|---|---|---|
| Método de Definição | def get_xxx() |
class Xxx: + __init__ |
| Casos de Uso | Lógica simples, chamada única | Requer estado, múltiplos métodos |
| Análise de Parâmetros | Análise Automática de Parâmetros de Função | Análise Automática de Parâmetros do __init__ |
| Reusabilidade | Alta | Média |
| Nível de Recomendação | Primeira Escolha | Usar em Cenários Complexos |
4. Subdependências Aninhadas
(1) Árvore de Resolução de Dependências
graph TD
A[get_current_user] --> B[get_db]
A --> C[get_token_from_header]
C --> D[OAuth2PasswordBearer]
E[require_admin] --> A
E --> F[check_subscription]
style A fill:#e1f5fe
style E fill:#fff3e0
O FastAPI analisa recursivamente a árvore de dependências: require_admin → get_current_user → get_db, com cada dependência instanciada apenas uma vez (em cache na mesma requisição).
(1) ▶ Exemplo: Subdependências Aninhadas—DB → Usuário
from fastapi import FastAPI, Depends, HTTPException, Header
app = FastAPI()
# Nível 1: Sessão do banco de dados
async def get_db():
db = {"connection": "active"}
try:
yield db
finally:
db["connection"] = "closed"
# Nível 2: Obter usuário atual (depende do DB)
async def get_current_user(
authorization: str = Header(...),
db: dict = Depends(get_db),
):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid token")
token = authorization.replace("Bearer ", "")
# Simplificado: em produção, verificar JWT
user = {"id": 1, "username": "alice", "role": "admin"}
return user
# Nível 3: Exigir admin (depende do usuário atual)
async def require_admin(user: dict = Depends(get_current_user)):
if user["role"] != "admin":
raise HTTPException(status_code=403, detail="Admin required")
return user
@app.get("/admin/stats")
async def admin_stats(admin: dict = Depends(require_admin)):
return {"admin": admin["username"], "total_products": 1000000}
Saída:
# Função definida com sucesso
5. Ciclo de Vida da Dependência e yield
(1) Nível de Requisição vs. Nível de Aplicação
| Ciclo de Vida | Método de Declaração | Escopo | Usos Típicos |
|---|---|---|---|
| Nível de Requisição | def dep(): yield x; cleanup |
Criado e destruído em cada requisição | DB Session, conexão Redis |
| Nível de Aplicação | @app.on_event("startup") |
Criado quando a aplicação inicia | Inicialização do Engine, pool de conexões |
(1) ▶ Exemplo: Dependências yield e Limpeza de Recursos
from fastapi import FastAPI, Depends
app = FastAPI()
# Dependência com escopo de requisição e limpeza
async def get_db_session():
# Configuração: criar sessão
session = {"id": "session-123", "active": True}
print(f"DB session opened: {session['id']}")
try:
yield session # Isso é injetado no endpoint
finally:
# Limpeza: fechar sessão (executa após a resposta)
session["active"] = False
print(f"DB session closed: {session['id']}")
@app.get("/products")
async def list_products(db: dict = Depends(get_db_session)):
print(f"Using DB session: {db['id']}")
return {"session_id": db["id"]}
Saída (logs do servidor):
DB session opened: session-123
Using DB session: session-123
DB session closed: session-123
6. Dependências Globais e Dependências de Grupo de Rotas
(1) Declarações de Dependência com Escopos Diferentes
(1) ▶ Exemplo: Dependências de Grupo de Rotas
from fastapi import FastAPI, Depends, APIRouter, Header, HTTPException
async def verify_api_key(x_api_key: str = Header(...)):
if x_api_key != "secret-key-123":
raise HTTPException(status_code=401, detail="Invalid API key")
return x_api_key
app = FastAPI()
# Rotas públicas - sem autenticação necessária
public_router = APIRouter()
@public_router.get("/health")
async def health():
return {"status": "healthy"}
# Rotas protegidas - chave API necessária para todas as rotas neste grupo
protected_router = APIRouter(dependencies=[Depends(verify_api_key)])
@protected_router.get("/products")
async def list_products():
return [{"id": 1, "name": "Widget"}]
@protected_router.post("/products")
async def create_product():
return {"id": 2, "name": "New Product"}
# Registrar roteadores
app.include_router(public_router)
app.include_router(protected_router, prefix="/api/v1")
Saída:
# Função definida com sucesso
| Escopo de Dependência | Local de Declaração | Escopo de Efeito |
|---|---|---|
| Nível de Endpoint | @app.get("/", dependencies=[...]) |
Endpoint Único |
| Nível de Grupo de Rotas | APIRouter(dependencies=[...]) |
Todos os endpoints no grupo de rotas |
| Global | FastAPI(dependencies=[...]) |
Todos os endpoints da aplicação |
7. Substituição de Dependências e Testes
(1) dependency_overrides
Durante os testes, é necessário substituir dependências de produção (como conexões de banco de dados e APIs externas); app.dependency_overrides permite substituir implementações de dependência sem modificar o código-fonte.
(1) ▶ Exemplo: Substituindo Dependências de Banco de Dados em Testes
from fastapi.testclient import TestClient
from fastapi import FastAPI, Depends
app = FastAPI()
# Dependência real
async def get_db():
return {"type": "postgresql", "host": "prod-db"}
# Dependência mock para testes
def get_mock_db():
return {"type": "sqlite", "host": "memory"}
@app.get("/db-info")
async def db_info(db: dict = Depends(get_db)):
return db
# Nos testes:
def test_db_info():
app.dependency_overrides[get_db] = get_mock_db
client = TestClient(app)
response = client.get("/db-info")
assert response.json() == {"type": "sqlite", "host": "memory"}
# Limpar
app.dependency_overrides.clear()
Saída:
# Função definida com sucesso
❓ Perguntas Frequentes
yield é executada?try/finally).dependency_overrides afeta outros testes?app. Certifique-se de chamar app.dependency_overrides.clear() ou usar fixtures para gerenciá-lo após o teste ser concluído.📖 Resumo
Depends()extrai lógica repetitiva em funções auxiliares ou classes reutilizáveis; endpoints só precisam declará-las e não precisam se preocupar com a implementação- Resolução recursiva automática de subdependências; cache dentro da mesma requisição para evitar execuções duplicadas
yieldimplementa gerenciamento de ciclo de vida de nível de requisição para dependências, garantindo que criação e limpeza de recursos sejam pareadas- Dependências de grupo de rotas (
APIRouter(dependencies=[...])) permitem que um grupo de endpoints compartilhe lógica de autenticação e validação app.dependency_overridesé uma ferramenta de teste poderosa que substitui as dependências reais de todos os endpoints com uma única linha de código
📝 Exercícios
- Problema Básico (Dificuldade ⭐): Crie uma função auxiliar
get_dbque retorne um dicionário simulando uma conexão de banco de dados, e injete e use-a em ambos os endpoints. Dica:db: dict = Depends(get_db) - Problema Avançado (Dificuldade ⭐⭐): Implemente dois níveis de dependências aninhadas:
get_db→get_current_user(ler o cabeçalho Authorization da requisição), e injete as informações do usuário atual no endpoint/me. Dica:user: dict = Depends(get_current_user) - Desafio (Dificuldade ⭐⭐⭐): Implemente uma cadeia DI de três camadas para o PriceTracker:
get_db(yield + limpeza) →get_current_user(verificar token) →require_subscription("pro")(verificar nível de assinatura), e substitua a dependência DB comdependency_overridesdurante os testes. Dica: dependênciayield+app.dependency_overrides[get_db] = mock_fn
---|



