sqlalchemy/alembic · error · NotImplementedError

No support for ALTER of constraints in SQLite dialect…

Error message

No support for ALTER of constraints in SQLite dialect. Please refer to the batch mode feature which allows for SQLite migrations using a copy-and-move strategy.

What it means

SQLiteImpl.add_constraint raises NotImplementedError because SQLite does not support ALTER TABLE ADD CONSTRAINT for foreign keys, unique constraints, checks, or primary keys after table creation. Alembic directs you to batch mode, which recreates the table with the new constraint using a copy-and-move strategy.

Solutions

  1. Wrap the operation in batch mode: with op.batch_alter_table('t') as batch_op: batch_op.create_foreign_key(...).
  2. Set recreate='always' in batch_alter_table when the constraint change is not auto-detected as needing a rebuild.
  3. Design test SQLite schemas to match constraints at table-creation time to avoid needing ALTER.

Example fix

# before (fails on SQLite)
op.create_foreign_key('fk_a_b', 'a', 'b', ['b_id'], ['id'])

# after
with op.batch_alter_table('a', schema=None) as batch_op:
    batch_op.create_foreign_key('fk_a_b', 'b', ['b_id'], ['id'])
Defensive patterns

Strategy: validation

Validate before calling

def add_constraint_sqlite_safe(op, table_name, fn):
    with op.batch_alter_table(table_name) as batch_op:
        fn(batch_op)

Type guard

from sqlalchemy.dialects import sqlite

def dialect_needs_batch(dialect) -> bool:
    return dialect.name == 'sqlite'

Prevention

When it happens

Trigger: Calling op.create_foreign_key(), op.create_unique_constraint(), op.create_check_constraint(), or op.create_primary_key() against a SQLite database outside of a batch_alter_table() context.

Common situations: Running migrations developed against PostgreSQL/MySQL on a SQLite test database; CI using in-memory SQLite where migrations assume full ALTER support.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/3620ecdfc4730c76. Report an issue: GitHub.

Appendix: source

Thrown at alembic/ddl/sqlite.py:78

                if isinstance(
                    col.server_default, schema.DefaultClause
                ) and isinstance(col.server_default.arg, sql.ClauseElement):
                    return True
                elif (
                    isinstance(col.server_default, Computed)
                    and col.server_default.persisted
                ):
                    return True
            elif op[0] not in ("create_index", "drop_index"):
                return True
        else:
            return False

    def add_constraint(self, const: Constraint, **kw: Any):
        # attempt to distinguish between an
        # auto-gen constraint and an explicit one
        if const._create_rule is None:
            raise NotImplementedError(
                "No support for ALTER of constraints in SQLite dialect. "
                "Please refer to the batch mode feature which allows for "
                "SQLite migrations using a copy-and-move strategy."
            )
        elif const._create_rule(self):
            util.warn(
                "Skipping unsupported ALTER for "
                "creation of implicit constraint. "
                "Please refer to the batch mode feature which allows for "
                "SQLite migrations using a copy-and-move strategy."
            )

    def drop_constraint(self, const: Constraint, **kw: Any):
        if const._create_rule is None:
            raise NotImplementedError(
                "No support for ALTER of constraints in SQLite dialect. "
                "Please refer to the batch mode feature which allows for "
                "SQLite migrations using a copy-and-move strategy."

View on GitHub (pinned to 5551b5d35f)