{"id":"42a6b943b5b896a5","repo":"sqlalchemy/alembic","slug":"can-t-create-table-in-batch-mode","errorCode":null,"errorMessage":"Can't create table in batch mode","messagePattern":"Can't create table in batch mode","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"alembic/operations/batch.py","lineNumber":198,"sourceCode":"        self.batch.append((\"drop_constraint\", (const,), {}))\n\n    def rename_table(self, *arg, **kw):\n        self.batch.append((\"rename_table\", arg, kw))\n\n    def create_index(self, idx: Index, **kw: Any) -> None:\n        self.batch.append((\"create_index\", (idx,), kw))\n\n    def drop_index(self, idx: Index, **kw: Any) -> None:\n        self.batch.append((\"drop_index\", (idx,), kw))\n\n    def create_table_comment(self, table):\n        self.batch.append((\"create_table_comment\", (table,), {}))\n\n    def drop_table_comment(self, table):\n        self.batch.append((\"drop_table_comment\", (table,), {}))\n\n    def create_table(self, table):\n        raise NotImplementedError(\"Can't create table in batch mode\")\n\n    def drop_table(self, table):\n        raise NotImplementedError(\"Can't drop table in batch mode\")\n\n    def create_column_comment(self, column):\n        self.batch.append((\"create_column_comment\", (column,), {}))\n\n\nclass ApplyBatchImpl:\n    def __init__(\n        self,\n        impl: DefaultImpl,\n        table: Table,\n        table_args: tuple,\n        table_kwargs: dict[str, Any],\n        reflected: bool,\n        partial_reordering: tuple = (),\n    ) -> None:","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/operations/batch.py#L180-L216","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.create_table('audit', sa.Column('id', sa.Integer))\n// after\nop.create_table('audit', sa.Column('id', sa.Integer))\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.add_column(sa.Column('email', sa.String))","handlingStrategy":"type-guard","validationCode":"from alembic.operations.base import BatchOperations\nif isinstance(batch_op, BatchOperations):\n    raise RuntimeError(\"create_table not allowed in batch mode; call op.create_table outside the batch context\")","typeGuard":"def is_batch_op(obj) -> bool:\n    from alembic.operations.base import BatchOperations\n    return isinstance(obj, BatchOperations)","tryCatchPattern":null,"preventionTips":["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."],"tags":["batch-mode","create-table","operations"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}