sqlalchemy/alembic · error · NotImplementedError

Can't drop table in batch mode

Error message

Can't drop table in batch mode

What it means

Raised by BatchOperationsImpl.drop_table() when op.drop_table() is invoked inside a with op.batch_alter_table(...) block. Batch mode operates on a single existing table's columns, constraints and indexes; it cannot drop the table under alteration because the whole batch contract is built around recreating one table. The method is a deliberate stub so callers fail fast rather than produce corrupt DDL.

Source

Thrown at alembic/operations/batch.py:201

        self.batch.append(("rename_table", arg, kw))

    def create_index(self, idx: Index, **kw: Any) -> None:
        self.batch.append(("create_index", (idx,), kw))

    def drop_index(self, idx: Index, **kw: Any) -> None:
        self.batch.append(("drop_index", (idx,), kw))

    def create_table_comment(self, table):
        self.batch.append(("create_table_comment", (table,), {}))

    def drop_table_comment(self, table):
        self.batch.append(("drop_table_comment", (table,), {}))

    def create_table(self, table):
        raise NotImplementedError("Can't create table in batch mode")

    def drop_table(self, table):
        raise NotImplementedError("Can't drop table in batch mode")

    def create_column_comment(self, column):
        self.batch.append(("create_column_comment", (column,), {}))


class ApplyBatchImpl:
    def __init__(
        self,
        impl: DefaultImpl,
        table: Table,
        table_args: tuple,
        table_kwargs: dict[str, Any],
        reflected: bool,
        partial_reordering: tuple = (),
    ) -> None:
        self.impl = impl
        self.table = table  # this is a Table object
        self.table_args = table_args

View on GitHub (pinned to 44fb345033)

Solutions

  1. Move the op.drop_table('t') call outside / after the with op.batch_alter_table(...) block, using the top-level op object.
  2. If you genuinely want to remove the table, drop it with op.drop_table('t') in the normal (non-batch) migration context and remove the batch_alter_table block entirely.
  3. Audit the migration script to confirm no helper function forwards drop_table into the batch context.

Example fix

// before
with op.batch_alter_table('accounts') as batch_op:
    batch_op.alter_column('accounts', 'name', type_=sa.String(100))
    batch_op.drop_table('accounts')  # raises NotImplementedError

// after
with op.batch_alter_table('accounts') as batch_op:
    batch_op.alter_column('accounts', 'name', type_=sa.String(100))
op.drop_table('accounts')
Defensive patterns

Strategy: validation

Validate before calling

# Guard before entering the batch block: never route drop_table through batch
import inspect
# simplest: structurally keep drop_table outside batch_alter_table:
# instead of batch_op.drop_table, use op.drop_table after the with-block

Type guard

def is_batch_context(op) -> bool:
    from alembic.operations.batch import BatchOperationsImpl
    return isinstance(op.get_context().impl, BatchOperationsImpl) if hasattr(op, 'get_context') else False

Try / catch

try:
    batch_op.drop_table('t')
except NotImplementedError:
    # fall back to top-level op outside the batch context
    op.drop_table('t')

Prevention

When it happens

Trigger: Calling op.drop_table('t') (or batch_op.drop_table('t')) while inside an active op.batch_alter_table('t') context manager. Any code path that routes a drop-table operation through the BatchOperationsImpl, e.g. a generic loop that applies the same op list to both normal and batch contexts.

Common situations: Copy-pasting a drop_table() call into a batch_alter_table block during a refactor; generic migration helpers that assume every Operations method is available on the batch impl; autogenerated code merged incorrectly so a drop_table lands inside the batch block.

Related errors


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