Migration Hooks Reference
Framework M supports modular, app-specific hooks that execute during the declarative synchronization process. These hooks allow you to perform manual database preparation or post-sync cleanup without needing to manage full Alembic version files for every small change.
Hook Discovery
The migration engine automatically scans the migrations module (specifically migrations/__init__.py) of every app listed in your INSTALLED_APPS (or discovered via entry points).
File Location: your_app/migrations/__init__.py
Available Hooks
before_sync(engine: Engine)
Executed before the declarative DDL sync begins.
- When to use:
- Creating database schemas or extensions required by your DocTypes.
- Renaming legacy tables so the sync engine identifies them correctly.
- Pre-calculating data required for new columns.
- Arguments:
engine: A standard SQLAlchemyEngineinstance connected to the target database.
after_sync(engine: Engine)
Executed after the declarative DDL sync completes successfully.
- When to use:
- Populating default data for newly created tables.
- Creating complex database views or triggers that aren't managed by SQLAlchemy models.
- Updating system settings or caches that depend on the new schema.
- Arguments:
engine: A standard SQLAlchemyEngineinstance connected to the target database.
Example Implementation
# my_app/migrations/__init__.py
from sqlalchemy import text
def before_sync(engine):
with engine.begin() as conn:
# Ensure a specific Postgres extension is available
conn.execute(text("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";"))
print(" [my_app] Checked for UUID extension.")
def after_sync(engine):
with engine.begin() as conn:
# Populate a default record if the table was just created
conn.execute(text("""
INSERT INTO my_table (id, name)
SELECT 'default', 'System Default'
WHERE NOT EXISTS (SELECT 1 FROM my_table WHERE id = 'default');
"""))
print(" [my_app] Ensured system default record.")
Safety and Idempotency
Hooks should always be idempotent. Since m migrate sync can be run multiple times (and is run automatically in m migrate all), your hooks must check for the existence of objects before creating or modifying them.
- Use
CREATE ... IF NOT EXISTS. - Use
INSERT ... WHERE NOT EXISTS. - Wrap destructive operations in checks.
Execution Order and Independence
When running m migrate all or m migrate sync:
- All
before_synchooks from all discovered apps are executed. - The Declarative Sync Engine performs the DDL synchronization.
- All
after_synchooks are executed.
[!IMPORTANT] Treat hooks as independent. While hooks currently execute in the order apps are loaded, you should never design hooks that depend on the execution of a hook in another app. Each hook should be self-contained and assume it is running in isolation. If you have cross-app dependencies, consider using an explicit Alembic migration with
depends_on.