{"id":"a6e1d29a71a4eadb","repo":"sqlalchemy/alembic","slug":"autogenerate-rendering-of-sql-expression-language","errorCode":null,"errorMessage":"Autogenerate rendering of SQL Expression language constructs not supported here; please use a plain SQL string","messagePattern":"Autogenerate rendering of SQL Expression language constructs not supported here; please use a plain SQL string","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"alembic/autogenerate/render.py","lineNumber":1186,"sourceCode":"            (\"name\", repr(_render_gen_name(autogen_context, constraint.name)))\n        )\n    return \"%(prefix)sCheckConstraint(%(sqltext)s%(opts)s)\" % {\n        \"prefix\": _sqlalchemy_autogenerate_prefix(autogen_context),\n        \"opts\": (\n            \", \" + (\", \".join(\"%s=%s\" % (k, v) for k, v in opts))\n            if opts\n            else \"\"\n        ),\n        \"sqltext\": _render_potential_expr(\n            constraint.sqltext, autogen_context, wrap_in_element=False\n        ),\n    }\n\n\n@renderers.dispatch_for(ops.ExecuteSQLOp)\ndef _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str:\n    if not isinstance(op.sqltext, str):\n        raise NotImplementedError(\n            \"Autogenerate rendering of SQL Expression language constructs \"\n            \"not supported here; please use a plain SQL string\"\n        )\n    return \"{prefix}execute({sqltext!r})\".format(\n        prefix=_alembic_autogenerate_prefix(autogen_context),\n        sqltext=op.sqltext,\n    )\n\n\nrenderers = default_renderers.branch()\n","sourceCodeStart":1168,"sourceCodeEnd":1197,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/autogenerate/render.py#L1168-L1197","documentation":"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('...').","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nop.execute(users_table.update().values(active=True))\n// after\nop.execute('UPDATE users SET active = true')","handlingStrategy":"type-guard","validationCode":"from alembic.operations import ops\nsql = my_op.sqltext if isinstance(my_op, ops.ExecuteSQLOp) else None\nif sql is not None and not isinstance(sql, str):\n    sql = str(sql)  # or raise, since rendering will fail","typeGuard":"def is_plain_string_sql(op) -> bool:\n    return getattr(op, \"sqltext\", None) is not None and isinstance(op.sqltext, str)","tryCatchPattern":null,"preventionTips":["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."],"tags":["autogenerate","rendering","sql-expression","execute"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}