sqlalchemy/alembic · error · NotImplementedError

SQLAlchemy 2.0 required

Error message

SQLAlchemy 2.0 required

What it means

Raised by the drop_constraint implementation when operation.if_exists is set but sqla_2 is False (SQLAlchemy < 2.0 installed). The IF EXISTS clause for DROP CONSTRAINT was only added to Alembic's path via SQLAlchemy 2.0 facilities, so requesting if_exists=True on an older SQLAlchemy is unsupported.

Source

Thrown at alembic/operations/toimpl.py:233


@Operations.implementation_for(ops.AddConstraintOp)
def create_constraint(
    operations: "Operations", operation: "ops.AddConstraintOp"
) -> None:
    operations.impl.add_constraint(
        operation.to_constraint(operations.migration_context)
    )


@Operations.implementation_for(ops.DropConstraintOp)
def drop_constraint(
    operations: "Operations", operation: "ops.DropConstraintOp"
) -> None:
    kw = {}
    if operation.if_exists is not None:
        if not sqla_2:
            raise NotImplementedError("SQLAlchemy 2.0 required")
        kw["if_exists"] = operation.if_exists
    operations.impl.drop_constraint(
        operations.schema_obj.generic_constraint(
            operation.constraint_name,
            operation.table_name,
            operation.constraint_type,
            schema=operation.schema,
        ),
        **kw,
    )


@Operations.implementation_for(ops.BulkInsertOp)
def bulk_insert(
    operations: "Operations", operation: "ops.BulkInsertOp"
) -> None:
    operations.impl.bulk_insert(  # type: ignore[union-attr]
        operation.table, operation.rows, multiinsert=operation.multiinsert

View on GitHub (pinned to 44fb345033)

Solutions

  1. Upgrade SQLAlchemy to >= 2.0.
  2. Remove the if_exists=True argument and guard the drop manually with an existence check instead.
  3. Pin the migration's requirements so SQLAlchemy >= 2.0 is guaranteed at runtime.

Example fix

// before (SQLAlchemy 1.4 installed)
op.drop_constraint('uq_user_email', 'user', if_exists=True)  # raises NotImplementedError

// after
sqlalchemy>=2.0 in requirements, then:
op.drop_constraint('uq_user_email', 'user', if_exists=True)
Defensive patterns

Strategy: validation

Validate before calling

from alembic.util.sqla_compat import sqla_2

def can_use_if_exists() -> bool:
    return bool(sqla_2)

Try / catch

from alembic.util.sqla_compat import sqla_2
if if_exists and not sqla_2:
    # guard manually instead of relying on if_exists
    if constraint_exists(bind, table, name):
        op.drop_constraint(name, table)
else:
    op.drop_constraint(name, table, if_exists=if_exists)

Prevention

When it happens

Trigger: Calling op.drop_constraint('c','t', if_exists=True) while SQLAlchemy < 2.0 is installed; setting if_exists on a DropConstraintOp in an environment pinned to SQLAlchemy 1.4.

Common situations: Pinning an older SQLAlchemy for compatibility with legacy apps; CI using a different SQLAlchemy version than production; copy-pasting an if_exists=True call from a 2.0-only codebase.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/b48faa1b6f493a97.json. Report an issue: GitHub.