Wiring a database into a Sillo application with setup_record: model registration, the connection lifecycle, per-request context, and the health check.
One call wires a database into an application.
from sillo import SilloAppfrom sillo.record import DatabaseConfig, setup_record
app = SilloApp()
setup_record( app, DatabaseConfig.from_env(), model_modules=["database.models", "sillo.admin.models"],)That does four things: builds a DatabaseManager, registers the model
modules, connects on startup and disconnects on shutdown, and puts the manager
on app.state["record"].
Why app.state matters
Section titled “Why app.state matters”The manager on app.state is what the sillo command looks for. Find
one and the db:* migration commands and the user:* account commands appear;
find none and they do not.
So the wiring you were writing anyway is what gives you the tooling. There is no second place to configure it.
Model modules
Section titled “Model modules”model_modules=["database.models", "sillo.admin.models"]Dotted paths to modules containing models. Tortoise discovers models by importing these, a model in a module not listed here has no table, and the error you get is a confusing one about a missing relation rather than a missing model.
sillo.admin.models is required if you mount the admin panel:
it holds the activity log every admin site writes to.
Installing the driver
Section titled “Installing the driver”Record depends on Tortoise; Tortoise needs a driver per backend, and none is installed by default:
uv add aiosqlite # SQLiteuv add asyncpg # PostgreSQLuv add asyncmy # MySQL / MariaDBPlus the extra itself:
uv add "sillo-framework[record]"The lifecycle
Section titled “The lifecycle”| Phase | What happens |
|---|---|
| Startup | init() connects, and generates schemas if configured to |
| Per request | ensure_context middleware makes the connection available |
| Shutdown | shutdown() closes the connections |
setup_record registers all three. You do not call them.
ensure_context
Section titled “ensure_context”Registered as middleware. Tortoise keeps its connection in a context variable, and an ASGI application handling concurrent requests needs that variable set for the task the handler runs in. This is what does it.
The practical consequence: a model call from inside a request works. A model
call from a background task that escaped the request (a bare
asyncio.create_task) may not, because it is a different task with a different
context. Use background tasks, which carry it.
Using the manager directly
Section titled “Using the manager directly”database = app.state["record"]
await database.health() # True when the connection answersconfig = database.orm_config() # the dict Tortoise/Aerich wanthealth() runs a trivial query and returns a boolean rather than raising,
which is what a /health endpoint wants:
@app.get("/health")async def health(request, response): ok = await app.state["record"].health() return response.json({"database": ok}, status=200 if ok else 503)Building one by hand
Section titled “Building one by hand”setup_record is a convenience. The pieces are public:
from sillo.record import DatabaseConfig, DatabaseManager
database = DatabaseManager(DatabaseConfig.sqlite("storage/app.db"))database.register_models("database.models")database.set_migrations("database.migrations")
await database.init()try: ...finally: await database.shutdown()Which is what a script or a standalone console needs. Nothing has started an application there, so nothing has connected.
Migrations
Section titled “Migrations”set_migrations names the package migrations are written to and read from.
setup_record defaults it to the conventional location; override it when your
project puts them elsewhere.
database.set_migrations("database.migrations")See Migrations.
Schema generation
Section titled “Schema generation”DB_GENERATE_SCHEMAS (default true) creates tables from the models at
startup when they do not exist.
That is right for tests and for a first run, and wrong for anything with data in it. It creates missing tables and does nothing about the ones whose shape has changed, which is exactly the divergence migrations exist to prevent.
Turn it off wherever you run migrations:
DB_GENERATE_SCHEMAS=falseSee also
Section titled “See also”- Configuration: every setting and its environment variable.
- Database commands: the CLI this setup unlocks.