Skip to content

Database migrations

bluefox-core provides Alembic integration that handles async engine configuration, model discovery, and database URL normalization automatically.

Setup

Initialize Alembic in your project:

uv run alembic init -t async migrations

Replace the generated migrations/env.py with:

from alembic import context
from bluefox_core.migrations import configure_alembic

configure_alembic(context)

That's it. The helper reads your DATABASE_URL from .env, discovers your models, and configures the async engine.

A ready-to-copy template is also available at bluefox_core/migrations/env_template.py.

Model discovery

configure_alembic() automatically discovers your models — no configuration required. Just put your models in a models.py file and inherit from BluefoxBase.

Convention (zero config)

bluefox-core scans your project root for models.py files in these locations:

  • models.py
  • app/models.py
  • */models.py (any top-level package)

All matching files are imported automatically. Your models are registered in BluefoxBase.metadata on import, which is how Alembic detects your tables.

# models.py (or app/models.py, myapp/models.py, etc.)
from sqlalchemy.orm import Mapped, mapped_column
from bluefox_core import BluefoxBase

class User(BluefoxBase):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(unique=True)
    name: Mapped[str]

Multiple models.py files across packages are discovered automatically — no need for a central re-export file. For example, both users/models.py and posts/models.py will be found and imported.

Explicit module (override)

If your models don't follow the convention, set MODELS_MODULE in your .env:

MODELS_MODULE=myapp.db.models

When set, only this module is imported — convention scanning is skipped.

Inheritance lint check

bluefox-core validates that your models inherit from BluefoxBase. If a model inherits from SQLAlchemy's DeclarativeBase directly (or a different base), you'll see a warning:

WARNING Model myapp.models.User inherits from DeclarativeBase but not BluefoxBase.
It won't be tracked by Bluefox migrations.
Change it to inherit from BluefoxBase instead.

This catches a common mistake where models exist but don't show up in migrations because they use the wrong base class.

Creating migrations

After changing your models, generate a migration:

make migrate-make m="add users table"

This creates a file in migrations/versions/. Review it before applying — autogenerate is good but not perfect.

Running migrations

# Apply all pending migrations
make migrate

# Roll back one migration
make migrate-down

Or using alembic directly for more control:

# Apply one migration forward
uv run alembic upgrade +1

# Roll back to the beginning
uv run alembic downgrade base

Checking status

# Show current revision
make migrate-status

# Show migration history
make migrate-history

# Show head revisions
uv run alembic heads

Makefile reference

Command Description
make migrate Apply all pending migrations
make migrate-make m="description" Create a new autogenerated migration
make migrate-down Roll back one migration
make migrate-status Show current migration revision
make migrate-history Show full migration history

Offline mode

For generating SQL scripts without a database connection:

uv run alembic upgrade head --sql > migration.sql

configure_alembic() handles offline mode automatically, using DATABASE_URL for the dialect.