{"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/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/operations/batch.py#L183-L219","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Move the op.drop_table('t') call outside / after the with op.batch_alter_table(...) block, using the top-level op object.","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.","Audit the migration script to confirm no helper function forwards drop_table into the batch context."],"exampleFix":"// before\nwith op.batch_alter_table('accounts') as batch_op:\n    batch_op.alter_column('accounts', 'name', type_=sa.String(100))\n    batch_op.drop_table('accounts')  # raises NotImplementedError\n\n// after\nwith op.batch_alter_table('accounts') as batch_op:\n    batch_op.alter_column('accounts', 'name', type_=sa.String(100))\nop.drop_table('accounts')","handlingStrategy":"validation","validationCode":"# Guard before entering the batch block: never route drop_table through batch\nimport inspect\n# simplest: structurally keep drop_table outside batch_alter_table:\n# instead of batch_op.drop_table, use op.drop_table after the with-block","typeGuard":"def is_batch_context(op) -> bool:\n    from alembic.operations.batch import BatchOperationsImpl\n    return isinstance(op.get_context().impl, BatchOperationsImpl) if hasattr(op, 'get_context') else False","tryCatchPattern":"try:\n    batch_op.drop_table('t')\nexcept NotImplementedError:\n    # fall back to top-level op outside the batch context\n    op.drop_table('t')","preventionTips":["Keep table-level DDL (create/drop table) outside batch_alter_table blocks.","Review any helper that forwards an op list into both normal and batch contexts.","Treat batch mode as column/constraint/index-only by convention."],"tags":["alembic","batch-mode","ddl","migrations","api-misuse"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}