Skip to content

Backfills and Seeds

Backfills

Creating Your First Backfill

alembic-environment has the ability to scope backfills to specific migrations, so the backfill will only run as the environment upgrades to head.

Let's create our first backfill:

uv run python -m migrations backfill

As you can see, without any arguments or flags, it scopes to the latest migration:

Wrote backfill stub C:\Users\miles\PycharmPro
jects\alembic-environment\database\migrations
\s

We can see the backfill file we generated in the ./database/migrations/src/migrations/backfills directory:

from sqlmodel import Session
from database_core import backfill


@backfill("2a7d456bae34")
def backfill_2a7d456bae34(session: Session) -> None:
    ...

It passes a raw Session from sqlmodel. Don't worry about doing session.commit(), because our alembic template already handles that:

"""initial

Revision ID: 2a7d456bae34
Revises:
Create Date: 2026-08-06 12:24:06.697441

"""

from typing import Sequence, Union

from database_util.utils import run_backfill


revision: str = "2a7d456bae34"
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###
    run_backfill(revision)


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###

As you can see, after the alembic commands, we call run_backfill. Do not remove this call, because it will break automatic backfilling.

Other capabilities

You can also scope backfills to specific migrations using the --for flag.

You can generate a new empty migration just for a backfill by using the --message flag.

Run uv run python -m migrations backfill --help for more information.

Seeding

alembic-environment also supports seeding in either the dev or prod environments. Read more about environments here.

Info

Why not in the staging environment?

The staging environment is setup to automatically copy your instance of prod with custom sanitization. Therefore, we don't need to seed it, because it already has a custom seeding flow.

Creating Your First Seed

We can create a new seed for a specific environment by running uv run python -m migrations seed {{env}} --generate {{ name }}

Warning

If we just run the bare uv run python -m migrations seed, it will attempt to seed that environment.

Because the seed command is repeatable, seed scripts should be idempotent

Let's go ahead and generate a seed for the dev environment:

uv run python -m migrations seed dev -g "my_first_seed"

Our seed files live in the ./database/migrations/src/migrations/seeds directory:

from models import *
from sqlmodel import Session
from database_core import seed

@seed(['dev'])
def my_first_seed(session: Session) -> None: 
    ...

If we want to pass in another environment, so that it seeds both, we can specify it in the seed decorator:

from models import *
from sqlmodel import Session
from database_core import seed

@seed(['dev', 'prod'])
def my_first_seed(session: Session) -> None: 
    ...

Running Your Seeds

We can either run seeds manually via: uv run python -m migrations seed {{ dev | prod }} or via uv run python -m environment up {{ dev | prod }}

For example, let's run our seed on the dev database automatically:

Running startup steps... [2/2]
Seeding 'dev' environment [1/1]
Completed 1 steps successfully.
Successfully ran 1 seeding functions in 'dev' environment.
Completed 2 steps successfully.

This way, because it's already built in to the up command, we don't have to worry about manually seeding.