Database Migrations
granite-assets ships with a built-in Alembic configuration so that its own schema migrations — added as it gains database features such as asset metadata storage — can be applied directly from host applications without any extra configuration.
How it works
When you install granite-assets and call upgrade_to_head(), Alembic runs the
revision files bundled inside the wheel (granite_assets/alembic/versions/)
against your database. A dedicated version table called
granite_assets_alembic_version is used, completely separate from the
alembic_version table that your own project manages. The two revision
histories never interfere with each other.
Note
Until granite-assets gains SQLAlchemy models, the function is a no-op (or near-no-op): it simply stamps the version table if it does not exist yet, then returns immediately. It is safe to call on every deployment.
Installation
The migration helpers require alembic and sqlalchemy, which are
not included in the default install to keep the footprint small.
Install the db optional extra:
pip install "granite-assets[db]"
# or with uv:
uv add "granite-assets[db]"
Calling upgrade_to_head()
from granite_assets.migrations import upgrade_to_head
# Pass a *synchronous* SQLAlchemy URL.
# Async drivers (asyncpg, aiosqlite) must be converted first — see below.
upgrade_to_head("postgresql+psycopg2://user:pass@localhost/mydb")
If your application uses an async driver (e.g. asyncpg), replace the
scheme before passing it to upgrade_to_head():
db_url = settings.db_url # "postgresql+asyncpg://..."
sync_url = db_url.replace("postgresql+asyncpg://", "postgresql+psycopg2://")
upgrade_to_head(sync_url)
Integration with host application migrations
The recommended pattern is to run granite-assets migrations before the host application’s own Alembic upgrade so that any foreign-key references to granite-assets tables are satisfied when the host schema is applied.
scripts/migrate.py (create one in your project):
from granite_assets.migrations import upgrade_to_head
from alembic.config import Config
from alembic import command
def main(sync_url: str) -> None:
# 1. granite-assets schema first
upgrade_to_head(sync_url)
# 2. host application schema second
cfg = Config("alembic.ini")
command.upgrade(cfg, "head")
Then wire it in your Makefile:
migrate:
uv run python scripts/migrate.py
Adding new revisions (for granite-assets maintainers)
When a new SQLAlchemy model is added to granite-assets, create a revision file inside the bundled alembic directory:
cd /path/to/granite-assets
uv run alembic --config src/granite_assets/alembic/alembic.ini \
revision --autogenerate -m "add asset metadata table"
Or, if you have a convenience wrapper in makefile:
make migrate-create MSG="add asset metadata table"
The generated file lands in
src/granite_assets/alembic/versions/ and is included in the next
wheel build automatically.
Tip
The version_table = "granite_assets_alembic_version" setting in
env.py ensures that running the above command against a database
that already contains a host application’s alembic_version table
does not overwrite or corrupt it.