Migrations

NextORM includes a built-in, file-based migration system. Migrations are plain Python modules with an upgrade(db) function. A schema snapshot is kept in migrations/.schema_snapshot.json so NextORM can diff the current entity schema against the last recorded state.

Workflow

# 1. Generate a migration file from the current entity schema diff
python -m nextorm.migrations.cli makemigrations --module myapp.models

# 2. Review and edit the generated migration if needed
# 3. Apply all pending migrations
python -m nextorm.migrations.cli migrate --module myapp.models

# 4. Check which migrations have been applied
python -m nextorm.migrations.cli showmigrations --module myapp.models

Or use the PDM shorthand (if configured in your pyproject.toml):

pdm run migrate --migrate

Migration files

Each migration file is a numbered Python module in the migrations directory:

migrations/
    .schema_snapshot.json       # auto-managed snapshot, commit this file
    0001_initial.py
    0002_add_email_to_user.py
    0003_create_tags_table.py

Example file:

# migrations/0002_add_email_to_user.py

def upgrade(db):
    db._execute_dml(
        "ALTER TABLE user ADD COLUMN email TEXT NOT NULL DEFAULT ''", []
    )
    db._execute_dml(
        "ALTER TABLE user ALTER COLUMN email DROP DEFAULT", []
    )

def downgrade(db):   # optional
    db._execute_dml("ALTER TABLE user DROP COLUMN email", [])

The db argument is the Database instance passed by the runner.

CLI reference

All commands require --module (the Python module that defines and binds the Database object) and accept --db-attr (attribute name in the module, default db) and --directory (default migrations/).

python -m nextorm.migrations.cli makemigrations --module myapp.models [--db-attr db] [--directory PATH] [--name LABEL]
python -m nextorm.migrations.cli migrate        --module myapp.models [--db-attr db] [--directory PATH] [--fake]
python -m nextorm.migrations.cli showmigrations --module myapp.models [--db-attr db] [--directory PATH]

makemigrations

Compares the current entity schema to the stored snapshot and writes a new migration file if there are any differences. Pass --name to give the file a descriptive suffix:

python -m nextorm.migrations.cli makemigrations --name add_post_status

If nothing has changed, the command exits without creating a file.

migrate

Applies every migration that has not yet been recorded in the _nextorm_migrations tracking table. Migrations are applied in ascending-number order.

The --fake flag records migrations as applied without executing the SQL — useful when adopting an existing schema:

python -m nextorm.migrations.cli migrate --fake

showmigrations

Lists all migration files with their application status:

[x] 0001_initial.py          (2025-03-10T14:22:00)
[x] 0002_add_email_to_user.py (2025-03-15T09:11:42)
[ ] 0003_create_tags_table.py

Python API

All CLI operations are available programmatically:

from nextorm import Database, migrate, makemigrations, MigrationRunner

db = Database(entities=[User, Post])
db.bind("sqlite", "app.db")
db.generate_mapping()

# Generate a migration file
makemigrations(db, directory="migrations/", name="initial")

# Apply pending migrations
migrate(db, directory="migrations/")

# Using the runner object for fine-grained control
runner = MigrationRunner(db, directory="migrations/")
statuses = runner.showmigrations()
runner.migrate()

Tracking table

Applied migrations are recorded in _nextorm_migrations — a table that NextORM creates automatically. Each row stores the migration version and a timestamp. Do not delete this table while you have applied migrations in production.

Schema snapshot

The file migrations/.schema_snapshot.json stores a JSON snapshot of the entity schema as it existed when the last migration was generated. Commit this file to version control — it is the baseline for future diffs.

Writing migration SQL

Migrations use raw SQL for schema changes so you have full control:

def upgrade(db):
    # Add a column with a temporary default so existing rows are valid
    db._execute_dml(
        "ALTER TABLE post ADD COLUMN slug VARCHAR(255) NOT NULL DEFAULT ''", []
    )
    # Remove the default so future INSERTs must provide the value
    db._execute_dml(
        "ALTER TABLE post ALTER COLUMN slug DROP DEFAULT", []
    )

Tip

For optional string columns (Opt[str]), no DEFAULT is needed for new tables because NextORM always provides an empty string. Only use the NOT NULL DEFAULT ''/DROP DEFAULT pattern when adding a column to an existing table.