sqlalchemy/alembic · error · NotImplementedError

Can't create table in batch mode

Error message

Can't create table in batch mode

What it means

BatchOperationsImpl.create_table raises NotImplementedError because creating an entirely new table is not meaningful inside a batch_alter_table block, which operates on an existing table by rebuilding it. The batch context only supports modifications to the target table, not creating additional tables.

Solutions

  1. Move op.create_table(...) outside the batch_alter_table block, calling it on the top-level Operations object.
  2. If the new table must be created alongside the batched alter, finish the batch block first, then create the table.
  3. Ensure code that conditionally creates tables does not run while a batch context is active.

Example fix

# before
with op.batch_alter_table('t') as batch_op:
    batch_op.alter_column('c', type_=sa.Integer())
    batch_op.create_table('other', ...)  # error

# after
with op.batch_alter_table('t') as batch_op:
    batch_op.alter_column('c', type_=sa.Integer())
op.create_table('other', ...)  # outside batch
Defensive patterns

Strategy: validation

Validate before calling

def safe_batch_block(op, table, batch_fn, *create_after):
    with op.batch_alter_table(table) as batch_op:
        batch_fn(batch_op)
    for create in create_after:
        op.create_table(create)  # always outside batch

Type guard

def is_outside_batch(op) -> bool:
    # batch ops proxy is not the top-level Operations instance
    return type(op).__name__ == 'Operations'

Prevention

When it happens

Trigger: Calling batch_op.create_table(...) (or having Alembic route create_table through the batch impl) inside a with op.batch_alter_table(...) block.

Common situations: Calling the wrong op alias inside a batch block; a generic code path that dispatches create_table uniformly across batch and non-batch contexts.

Related errors


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

Appendix: source

Thrown at alembic/operations/batch.py:198

        self.batch.append(("drop_constraint", (const,), {}))

    def rename_table(self, *arg, **kw):
        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:

View on GitHub (pinned to 5551b5d35f)