sqlalchemy/alembic · error · NotImplementedError

TODO

Error message

TODO

What it means

Raised as NotImplementedError('TODO') by ApplyBatchImpl.rename_table (batch.py:712-713). Renaming a table inside a batch_alter_table block is not implemented — batch mode is designed to alter the structure of one existing table, and a rename of the surrounding table is not a supported batch operation. The message is a literal placeholder indicating the feature was never built.

Source

Thrown at alembic/operations/batch.py:713

                # Operations.implementation_for(alter_column)
                return
            raise ValueError("No such constraint: '%s'" % const.name)
        else:
            if isinstance(const, PrimaryKeyConstraint):
                for col in const.columns:
                    self.columns[col.name].primary_key = False

    def create_index(self, idx: Index) -> None:
        self.new_indexes[idx.name] = idx  # type: ignore[index]

    def drop_index(self, idx: Index) -> None:
        try:
            del self.indexes[idx.name]  # type: ignore[arg-type]
        except KeyError:
            raise ValueError("No such index: '%s'" % idx.name)

    def rename_table(self, *arg, **kw):
        raise NotImplementedError("TODO")

View on GitHub (pinned to 5551b5d35f)

Solutions

  1. Perform the rename as a top-level op.rename_table('old', 'new') call, outside any batch context.
  2. If you also need structural changes, do the rename first at top level, then open batch_alter_table against the new name.
  3. Do not attempt to rename inside batch mode; there is no workaround.

Example fix

// before
with op.batch_alter_table('user') as batch_op:
    batch_op.rename_table('user', 'users')
// after
op.rename_table('user', 'users')
with op.batch_alter_table('users') as batch_op:
    batch_op.alter_column('email', existing_type=sa.String(255))
Defensive patterns

Strategy: validation

Validate before calling

# Disallow rename_table inside batch context by routing it to the top level.
def safe_op(op_name, *args, **kwargs):
    if op_name == 'rename_table':
        assert not _inside_batch, 'rename_table must run at top level, not in batch mode'
    return getattr(op, op_name)(*args, **kwargs)

op.rename_table('old', 'new')  # top-level, never inside batch

Type guard

from typing import Literal
NonBatchOp = Literal['rename_table', 'create_table', 'drop_table']
def must_run_top_level(op_name: str) -> bool:
    return op_name in ('rename_table', 'create_table', 'drop_table')

Prevention

When it happens

Trigger: Calling batch_op.rename_table('old', 'new') inside a `with op.batch_alter_table(...)` block. The ApplyBatchImpl class simply has no rename_table logic and raises immediately.

Common situations: Developers assume the batch proxy mirrors the full Operations API and try to combine a table rename with column alters in one batch. This is unsupported on all backends, not just SQLite.

Related errors


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