Skip to content

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.