Database Access ¶
Stario uses standard Python closures for dependency injection. This makes database access explicit and easy to test.
1. The Closure Pattern ¶
Create a factory function that takes your database client and returns handlers.
def user_handlers(db: Database):
async def get_user(c: Context, w: Writer):
user_id = c.req.tail
user = await db.find(user_id)
w.json(user)
return get_user
# Usage in main.py
db = Database("postgresql://...")
app.get("/users/*", user_handlers(db))
2. Router Factories ¶
For larger apps, create entire routers with their own dependencies.
def api_router(pool: Pool) -> Router:
r = Router()
async def list_items(c: Context, w: Writer):
async with pool.acquire() as conn:
items = await conn.fetch("SELECT * FROM items")
w.json(items)
r.get("/items", list_items)
return r
# Mounting
app.mount("/api", api_router(my_pool))
3. Connection Pooling (Production) ¶
Initialize your pool in async main() and pass it down.
async def main():
pool = await asyncpg.create_pool("...")
app = Stario()
app.mount("/api", api_router(pool))
await app.serve()
Why this wins? ¶
- No Globals: You don't need a global
dbobject. - Unit Testing: You can pass a
MockDatabase()into your factory during tests. - Explicit: Every handler shows exactly where its dependencies come from.
