Migrations¶
Migrations track changes to your model schemas and apply them to the database. AMSDAL auto-detects schema changes and generates migration files that can be applied, rolled back, and versioned.
Quick Start¶
# 1. Generate migrations from model changes
amsdal migrations new
# 2. Apply pending migrations
amsdal migrations apply
# 3. List all migrations and their status
amsdal migrations
Schema Migrations¶
When you add, modify, or remove fields from a model, run amsdal migrations new to generate a migration file:
amsdal migrations new
This compares the current model definitions against the last known schema and generates a migration file in src/migrations/:
📁 src
└── 📁 migrations
├── 📄 0000_initial.py
├── 📄 0001_update_class_person.py
└── 📄 0002_create_class_company.py
Migration files are named {NNNN}_{description}.py where NNNN is a zero-padded sequence number.
Generated Migration Example¶
from amsdal_models.migration import migrations
from amsdal_utils.models.enums import ModuleType
class Migration(migrations.Migration):
operations: list[migrations.Operation] = [
migrations.UpdateClass(
class_name="Person",
old_schema={...},
new_schema={...},
module_type=ModuleType.USER,
),
]
Schema Operations¶
| Operation | Description |
|---|---|
CreateClass |
Creates a new model/table |
UpdateClass |
Modifies an existing model schema |
DeleteClass |
Removes a model |
Data Migrations¶
For data transformations — populating new fields, restructuring data, or running one-time scripts — create a data migration:
amsdal migrations new --data --name populate_default_roles
This generates a template with forward_migration and backward_migration functions:
from amsdal_models.migration import migrations
def forward_migration(schemas: migrations.MigrationSchemas) -> None:
...
def backward_migration(schemas: migrations.MigrationSchemas) -> None:
...
class Migration(migrations.Migration):
operations: list[migrations.Operation] = [
migrations.MigrateData(
forward_migration=forward_migration,
backward_migration=backward_migration,
),
]
Accessing Models¶
Use schemas.get_model() to retrieve model classes inside a data migration. Models are compiled dynamically during migration execution — you cannot import them directly.
def forward_migration(schemas: migrations.MigrationSchemas) -> None:
User = schemas.get_model("User")
Role = schemas.get_model("Role")
admin_role = Role(name="admin")
admin_role.save()
for user in User.objects.filter(is_staff=True).execute():
user.role = admin_role.build_reference()
user.save()
Resolving Relationship Fields¶
Models retrieved via schemas.get_model() may have unresolved forward references for relationship fields. You must resolve them before using these relationships:
| Helper | Purpose |
|---|---|
complete_deferred_foreign_keys(Model) |
Resolve FK fields |
complete_deferred_primary_keys(Model) |
Resolve composite PKs that include FK components |
complete_deferred_many_to_many(Model) |
Resolve M2M fields |
from amsdal_models.classes.relationships.helpers.deferred_foreign_keys import (
complete_deferred_foreign_keys,
)
from amsdal_models.classes.relationships.helpers.deferred_primary_keys import (
complete_deferred_primary_keys,
)
from amsdal_models.classes.relationships.helpers.deferred_many_to_many import (
complete_deferred_many_to_many,
)
from amsdal_models.migration import migrations
def forward_migration(schemas: migrations.MigrationSchemas) -> None:
Order = schemas.get_model("Order")
Product = schemas.get_model("Product")
Tag = schemas.get_model("Tag")
# Resolve FK fields
complete_deferred_foreign_keys(Order)
complete_deferred_foreign_keys(Product)
# Resolve composite PKs if needed
complete_deferred_primary_keys(Order)
# Resolve M2M fields
complete_deferred_many_to_many(Product)
for order in Order.objects.all().execute():
product = order.product # FK field — works after resolution
...
Warning
Without calling the appropriate complete_deferred_* helpers, accessing relationship fields will raise errors or return unresolved ForwardRef objects.
Async Data Migrations¶
Important
If your application runs in async mode, your data migrations must be async as well. Sync migration functions will not work correctly in an async application.
Use async def and async ORM methods (aexecute(), asave(), adelete(), abuild_reference(), bulk_aupdate(), etc.):
from amsdal_models.classes.relationships.helpers.deferred_foreign_keys import (
complete_deferred_foreign_keys,
)
from amsdal_models.migration import migrations
async def forward_migration(schemas: migrations.MigrationSchemas) -> None:
Category = schemas.get_model("Category")
Product = schemas.get_model("Product")
complete_deferred_foreign_keys(Product)
default_category = Category(name="General")
await default_category.asave()
products = await Product.objects.filter(category=None).aexecute()
for product in products:
product.category = await default_category.abuild_reference()
await Product.objects.bulk_aupdate(products)
def backward_migration(schemas: migrations.MigrationSchemas) -> None: ...
class Migration(migrations.Migration):
operations: list[migrations.Operation] = [
migrations.MigrateData(
forward_migration=forward_migration,
backward_migration=backward_migration,
),
]
Tips¶
- Get all models needed first, then resolve deferred keys, then perform operations.
- Use batch operations (
bulk_acreate(),bulk_aupdate(),bulk_adelete()) for large datasets. - Backward migrations can be a no-op (
...) if rollback is not needed, but consider the implications. - Filter by metadata when needed:
filter(_metadata__is_deleted=False)to exclude soft-deleted records. - Use
.build_reference()(sync) or.abuild_reference()(async) to set FK fields on related objects.
Applying Migrations¶
# Apply all pending migrations
amsdal migrations apply
# Apply up to a specific migration number
amsdal migrations apply --number 0005
Migrations are applied in order: Core (AMSDAL framework) → Contrib (plugins) → App (your application).
Rollback¶
Roll back to a specific migration number by applying up to that number:
# Roll back to migration 0003 (unapplies 0004, 0005, etc.)
amsdal migrations apply --number 0003
# Unapply all migrations
amsdal migrations apply --number zero
Rollback executes the backward operations in reverse order.
Fake Apply¶
If you have manually applied schema changes and need the migration table to catch up:
amsdal migrations apply --number 0005 --fake
This marks migrations as applied without executing them.
CLI Reference¶
| Command | Alias | Description |
|---|---|---|
amsdal migrations |
amsdal migs |
List all migrations with status |
amsdal migrations new |
amsdal migs n |
Generate a new migration |
amsdal migrations new --data |
amsdal migs n -d |
Create a data migration |
amsdal migrations new --name NAME |
amsdal migs n -n NAME |
Custom migration name |
amsdal migrations apply |
amsdal migs apl |
Apply pending migrations |
amsdal migrations apply --number N |
Apply up to migration N | |
amsdal migrations apply --fake |
Fake apply (no DB changes) |
Server Startup¶
When running amsdal serve, the server checks for pending migrations before starting:
- If model changes are detected without a migration file → error: run
amsdal migrations new - If unapplied migrations exist → error: run
amsdal migrations apply
The server will not start until all migrations are applied.
Troubleshooting¶
No changes detected when running amsdal migrations new
Verify that your model changes are saved. The command compares the current model files against the last generated schema.
Need to manually fix the database schema
Apply your manual changes, then use --fake to mark the migration as applied without re-executing it.