sqlalchemy/alembic · error · NotImplementedError

Autogenerate rendering of SQL Expression language…

Error message

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

What it means

The ExecuteSQLOp renderer raises NotImplementedError when op.execute() receives a SQLAlchemy expression construct (not a plain string) during autogenerate. Alembic's autogenerate DDL rendering can only emit op.execute('literal sql string'); it cannot serialize arbitrary SQL Expression Language elements into migration source code.

Solutions

  1. Pass a plain SQL string to op.execute(): op.execute('UPDATE t SET col = 1').
  2. If you have a SQLAlchemy expression, compile it to a string first: op.execute(str(my_expr.compile(dialect=bind.dialect))).
  3. Keep data-migration execute() calls authored manually as raw SQL strings rather than expressions.

Example fix

# before
from sqlalchemy import text
op.execute(text('UPDATE accounts SET active = true'))

# after (plain string, autogenerate-safe)
op.execute('UPDATE accounts SET active = true')
Defensive patterns

Strategy: type-guard

Validate before calling

def execute_for_autogenerate(op, sql):
    if not isinstance(sql, str):
        sql = str(sql.compile(compile_kwargs={'literal_binds': True}))
    op.execute(sql)

Type guard

def is_plain_sql_string(value) -> bool:
    return isinstance(value, str)

Prevention

When it happens

Trigger: Calling op.execute(some_select_or_text_object) inside a revision that is being processed by autogenerate (e.g., via process_revision_directives) where the sqltext is a Select, Insert, or text() construct rather than a str.

Common situations: Custom process_revision_directives hooks that inject op.execute() calls with expression objects; data migrations authored with SQLAlchemy expressions that get swept into autogenerate output.

Related errors


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

Appendix: 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 5551b5d35f)