Database & ORM
axion ships optional SQLAlchemy integration for persistence. Enable it with:
pip install "axion-framework[db]"
Then use the shared db singleton from axion.db. All of this lives under a
single, re-usable connection object — no separate engine wiring required.
Connecting
The framework reads DATABASE_URL from settings (default:
sqlite:///db.sqlite3) and connects lazily on first use.
# settings.py
DATABASE_URL = "sqlite:///db.sqlite3"
In main.py, wire the database into the request lifecycle:
from axion import WebServer
from axion.db import db
app = WebServer(debug=True)
app.use_database()
use_database(url=None, database=None) registers the
DatabaseMiddleware (a transaction wrapper) and exposes the given Database
— or the shared axion.db.db singleton by default.
Defining models
Models subclass axion.db.models.Model, the declarative base:
from axion.db.models import Model
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
class User(Model):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
All models share one Model.metadata, so tables are created together. By
default tables are created automatically on first session use
(AUTO_CREATE_TABLES = True). Disable it to manage schemas yourself:
# settings.py
AUTO_CREATE_TABLES = False
Request-scoped sessions
db.session returns a thread-local session. Each worker thread gets its
own session, and DatabaseMiddleware commits on success, rolls back on
failure, and removes the session after every request.
Use sessions directly:
from axion.db import db
from axion.db.models import Model
def get_users():
return db.session.query(User).all()
def create_user(name):
user = User(name=name)
db.session.add(user)
db.session.flush()
return user
def delete_user(user_id):
user = db.session.get(User, user_id)
if user:
db.session.delete(user)
return user
Injecting a session into handlers
Annotate a handler argument with Session and axion injects the request’s
session automatically. Combined with the transaction middleware, writes are
committed for you:
from sqlalchemy.orm import Session
from axion import WebServer
from axion.http import Response
app = WebServer(debug=True)
app.use_database()
@app.route("/api/users", methods=["POST"])
def create_user(name: str, email: str, session: Session, response: Response):
user = User(name=name, email=email)
session.add(user)
session.flush()
response.json({"id": user.id, "name": user.name}, status=201)
@app.route("/api/users/<int:user_id>")
def get_user(user_id: int, session: Session, response: Response):
user = session.get(User, user_id)
if user is None:
response.send("User not found", 404)
return
response.json({"id": user.id, "name": user.name})
session.flush() sends the INSERT and populates auto-generated keys; the
commit happens automatically when the request succeeds. If the handler raises,
everything rolls back and a 500 is returned.
SQLite in-memory databases
sqlite:///:memory: uses a StaticPool with check_same_thread=False, so an
in-memory database is shared across the worker threads and survives across
requests. Ideal for tests.
db.configure("sqlite:///:memory:")
Database API
| Member | Description |
|---|---|
db.url / db.echo |
Connection settings |
db.engine |
The SQLAlchemy Engine (connects on first use) |
db.session |
The request-scoped, thread-local session |
db.connect() |
Create the engine and session factory |
db.configure(url=None, *, echo=None) |
Reconfigure, disposing any prior engine |
db.create_all() |
Create missing tables for all models |
db.drop_all() |
Drop all model tables |
db.remove() |
Remove the current thread’s session |
db.dispose() |
Close the engine and release pooled connections |
use_database() re-uses the shared singleton, but you can pass your own:
app.use_database(url="sqlite:///:memory:", database=my_db)
Configuring the transaction middleware manually
use_database() registers DatabaseMiddleware for you. To customize:
from axion.db import DatabaseMiddleware
app.use(DatabaseMiddleware(database=db))
Next steps
- Return rows from the database as JSON with serializers.
- See the example application (
models.py,services.py) for a full CRUD flow with a SQLite database.