sqlalchemy/alembic · error · NotImplementedError

Autogenerate rendering of SQL Expression language constructs

Error message

Autogenerate rendering of SQL Expression language constructs not supported here; please use a plain SQL string

What it means

The ExecuteSQLOp renderer only knows how to emit text SQL into a generated migration script; it cannot render arbitrary SQLAlchemy Core/ORM expression constructs (text() wrappers of expressions, select() objects, etc.) as Python source. The guard checks that op.sqltext is a plain str before formatting it as op.execute('...').

Source

Thrown at alembic/autogenerate/render.py:1186

            ("name", repr(_render_gen_name(autogen_context, constraint.name)))
        )
    return "%(prefix)sCheckConstraint(%(sqltext)s%(opts)s)" % {
        "prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
        "opts": (
            ", " + (", ".join("%s=%s" % (k, v) for k, v in opts))
            if opts
            else ""
        ),
        "sqltext": _render_potential_expr(
            constraint.sqltext, autogen_context, wrap_in_element=False
        ),
    }


@renderers.dispatch_for(ops.ExecuteSQLOp)
def _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str:
    if not isinstance(op.sqltext, str):
        raise NotImplementedError(
            "Autogenerate rendering of SQL Expression language constructs "
            "not supported here; please use a plain SQL string"
        )
    return "{prefix}execute({sqltext!r})".format(
        prefix=_alembic_autogenerate_prefix(autogen_context),
        sqltext=op.sqltext,
    )


renderers = default_renderers.branch()

View on GitHub (pinned to 44fb345033)

Solutions

  1. Pass a plain SQL string to op.execute(), e.g. op.execute('UPDATE users SET active = true').
  2. If you need SQLAlchemy expression objects at runtime, wrap them only inside hand-written upgrade()/downgrade() functions, not inside anything autogenerate is asked to render.
  3. Use sa.text('...') with a literal string body rather than nesting an expression.

Example fix

// before
op.execute(users_table.update().values(active=True))
// after
op.execute('UPDATE users SET active = true')
Defensive patterns

Strategy: type-guard

Validate before calling

from alembic.operations import ops
sql = my_op.sqltext if isinstance(my_op, ops.ExecuteSQLOp) else None
if sql is not None and not isinstance(sql, str):
    sql = str(sql)  # or raise, since rendering will fail

Type guard

def is_plain_string_sql(op) -> bool:
    return getattr(op, "sqltext", None) is not None and isinstance(op.sqltext, str)

Prevention

When it happens

Trigger: Calling op.execute() inside a migration with a non-string argument (e.g. a Column object, a select(), an Insert construct, a text() whose inner expression is not a literal string), then running 'alembic revision --autogenerate' which tries to render the queued op into the new revision file.

Common situations: A hand-written migration used op.execute(select(...)) or op.execute(text(table.insert())) and the user then runs autogenerate while that op is queued; passing a DDL construct that is not reducible to a literal SQL string.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/a6e1d29a71a4eadb.json. Report an issue: GitHub.