Alexandre Gomes Gaigalas – 12 de Fevereiro de 2026
Este post explica como usar o apywire para construir uma aplicação modular com injeção de dependências usando starlette. Vamos passo a passo para conectar serviços, handlers e um banco de dados usando um config.yaml declarativo.
O código completo deste tutorial está disponível em github.com/alganet/apywire-starlette.
Em muitas aplicações web Python, a cola entre dependências (conexões com banco, serviços e controllers) é feita manualmente no ponto de entrada da aplicação ou usando frameworks que dependem muito de reflection em tempo de execução e decorators. Isso pode causar:
O apywire resolve isso oferecendo uma maneira declarativa de definir o grafo de dependências em um arquivo YAML, que é então compilado em código Python eficiente e transparente.
Primeiro, definimos nossos componentes como classes Python simples. Note que elas não dependem de apywire nem de nenhum framework de DI — apenas declaram o que precisam em __init__.
src/services.py)class UserService:
def __init__(self, db):
self.db = db
def get_user(self, screen_name: str):
# ... logic to fetch user ...
pass
class MigrationService:
def __init__(self, db):
self.db = db
def run(self):
# ... logic to run migrations ...
pass
src/handlers.py)Os handlers (controllers) são callables simples que recebem dependências.
from starlette.responses import JSONResponse
from starlette.requests import Request
import services
class UserHandler:
def __init__(self, users: services.UserService):
self.users = users
async def __call__(self, scope, receive, send):
request = Request(scope, receive)
screen_name = request.path_params["screen_name"]
user = self.users.get_user(screen_name)
# ... return response ...
config.yaml)Aqui acontece a mágica. Declaramos nosso grafo de dependências em config.yaml.
# 1. Define the Database Connection
# We use apsw directly, no wrapper needed.
apsw.Connection db:
filename: "db.sqlite"
# 2. Wire the Services
# We inject the 'db' defined above into our services.
services.UserService users:
db: {db}
services.MigrationService migrations:
db: {db}
# 3. Wire the Handlers
# We inject the 'users' service into the UserHandler.
handlers.UserHandler user_handler:
users: {users}
handlers.HomeHandler home_handler: {}
# 4. Define Routes
# We bind the 'user_handler' to a path.
starlette.routing.Route user_route:
path: "/users/{screen_name}"
endpoint: {user_handler}
methods: ["GET"]
starlette.routing.Route hello_route:
path: "/"
endpoint: {home_handler}
# 5. The Application
# We assemble the app with our routes.
starlette.applications.starlette app:
routes:
- {user_route}
- {hello_route}
Principais observações:
{db}: referencia o componente chamado db.module.ClassName para especificar tipos.O apywire compila esse YAML em um arquivo Python (src/container.py). Esse código gerado é o que sua aplicação realmente usa — ele lida com carregamento preguiçoso e garante que singletons sejam compartilhados corretamente.
Para compilar:
python src/app.py --compile
Isso gera src/container.py. Você não edita esse arquivo, mas pode inspecioná‑lo para ver exatamente como sua aplicação foi ligada — é apenas código Python comum!
O entrypoint src/app.py simplesmente importa o container compilado e o utiliza.
from container import compiled
def create_app():
return compiled.app()
if __name__ == "__main__":
# ... CLI logic ...
uvicorn.run("app:create_app", factory=True)
Como compiled é importado de um arquivo gerado, não há overhead de framework em tempo de execução.
Ao isolar o wiring em config.yaml, alcançamos:
Experimente o apywire no seu próximo projeto Python!