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
- Pass a plain SQL string to op.execute(), e.g. op.execute('UPDATE users SET active = true').
- 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.
- 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
- Always pass a Python str literal to op.execute().
- Avoid op.execute(text(nested_expression)); use a literal SQL string.
- Lint migrations for op.execute() calls whose argument is not a string.
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
- can't return inspector as this AutogenContext has no databas
- Duplicate table keys across multiple MetaData objects: %s
- Can only return single object for UpgradeOps traverse
- Can only return single object for DowngradeOps traverse
- This MigrationScript instance has a multiple-entry list for
AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04).
Data as JSON: /data/errors/a6e1d29a71a4eadb.json.
Report an issue: GitHub.