sqlalchemy/alembic · error · NotImplementedError
Can't create table in batch mode
Error message
Can't create table in batch mode
What it means
Inside a batch_alter_table context the BatchOperationsImpl queues column/constraint/index ops to be replayed against the table being rebuilt; create_table is not one of them because the batch is already scoped to one existing table. Calling batch_op.create_table(...) is mapped to a method that raises immediately.
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 44fb345033)
Solutions
- Call op.create_table(...) at the top level of upgrade()/downgrade(), outside any batch_alter_table context.
- If the intent is to redefine the batched table, use add_column / create_*_constraint inside the batch, or recreate='always'.
- Split the migration: create the new table first, then batch_alter_table the existing one.
Example fix
// before
with op.batch_alter_table('users') as batch_op:
batch_op.create_table('audit', sa.Column('id', sa.Integer))
// after
op.create_table('audit', sa.Column('id', sa.Integer))
with op.batch_alter_table('users') as batch_op:
batch_op.add_column(sa.Column('email', sa.String)) Defensive patterns
Strategy: type-guard
Validate before calling
from alembic.operations.base import BatchOperations
if isinstance(batch_op, BatchOperations):
raise RuntimeError("create_table not allowed in batch mode; call op.create_table outside the batch context") Type guard
def is_batch_op(obj) -> bool:
from alembic.operations.base import BatchOperations
return isinstance(obj, BatchOperations) Prevention
- Call create_table on op at the top level of upgrade(), never inside batch_alter_table.
- If you need a new table, create it before entering the batch block.
- Lint migrations to reject batch_op.create_table / batch_op.drop_table.
When it happens
Trigger: Calling batch_op.create_table(table_obj) (or a dynamically-routed create operation) inside a 'with op.batch_alter_table("t") as batch_op:' block, instead of calling op.create_table at the top level.
Common situations: Autogenerated output that mistakenly nested a create_table in a batch block; a developer assuming batch_op mirrors the full Operations API; refactoring a migration by moving a create_table inside an existing batch_alter_table.
Related errors
- The %s method does not apply to a batch table alter operatio
- Individual alter column constructs not supported by MySQL
- No support for ALTER of constraints in SQLite dialect. Pleas
- recreate may be one of 'auto', 'always', or 'never'.
- Can't drop table in batch mode
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/42a6b943b5b896a5.json.
Report an issue: GitHub.