{"record":{"id":"a1b19f8377947aca","repo":"sqlalchemy/alembic","slug":"can-t-drop-table-in-batch-mode","errorCode":null,"errorMessage":"Can't drop table in batch mode","messagePattern":"Can't drop table in batch mode","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"alembic/operations/batch.py","lineNumber":201,"sourceCode":"        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:\n        self.impl = impl\n        self.table = table  # this is a Table object\n        self.table_args = table_args","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/operations/batch.py#L183-L219","documentation":"Raised as NotImplementedError by the batch operations proxy when drop_table() is called inside a with op.batch_alter_table(...) block. Batch mode reconstructs a single table by copying it; dropping a table is conceptually outside its scope, so the proxy (ApplyBatchImplType/BatchOperationsImpl at batch.py:200-201) refuses it explicitly. The companion create_table() raises the analogous 'Can't create table in batch mode' error.","triggerScenarios":"Calling op.drop_table('mytable') (or batch_op.drop_table(...)) while inside a `with op.batch_alter_table('mytable') as batch_op:` context manager. The batch context only proxies alter_column/add_column/drop_column/add_constraint/drop_constraint/create_index/drop_index; create_table and drop_table are deliberately stubbed out.","commonSituations":"Developers copy a sequence of op.* calls (including a table drop) and paste them inside a batch_alter_table block by mistake, or refactor an existing migration and forget that the drop belongs at the top level. Common when SQLite workarounds force batch mode for unrelated alters.","solutions":["Move the op.drop_table(...) call outside (after) the `with op.batch_alter_table(...)` block so it runs as a normal top-level operation.","If you intended to clear the table contents, use op.execute('DELETE FROM mytable') inside the batch block instead of drop_table.","If you are rewriting the whole schema, do not use batch_alter_table at all; emit the drop_table at module level."],"exampleFix":"// before\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.drop_table('users')  # wrong scope\n// after\nwith op.batch_alter_table('users') as batch_op:\n    batch_op.alter_column('email', existing_type=sa.String())\nop.drop_table('users')  # top-level call","handlingStrategy":"validation","validationCode":"# Before entering batch mode, ensure the op you intend to call is allowed.\n_BATCH_ALLOWED = {\n    'alter_column', 'add_column', 'drop_column',\n    'add_constraint', 'drop_constraint',\n    'create_index', 'drop_index',\n    'create_table_comment', 'drop_table_comment',\n    'create_column_comment',\n}\n\nif op_name in {'create_table', 'drop_table'}:\n    raise ValueError(f'{op_name} cannot run inside batch_alter_table; move it to top level')","typeGuard":"# narrow an operation name to the batch-safe set\nfrom typing import Literal\nBatchSafeOp = Literal[\n    'alter_column', 'add_column', 'drop_column',\n    'add_constraint', 'drop_constraint',\n    'create_index', 'drop_index',\n]\ndef is_batch_safe(op_name: str) -> bool:\n    return op_name in get_args(BatchSafeOp)","tryCatchPattern":null,"preventionTips":["Keep create_table / drop_table calls outside `with op.batch_alter_table(...)` blocks.","Review the batch proxy's allowed method list before nesting unfamiliar ops.","Unit-test migrations against SQLite to surface batch-mode incompatibilities early."],"tags":["alembic","batch-mode","database-migration","python"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}