{"record":{"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/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/autogenerate/render.py#L1168-L1197","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a plain SQL string to op.execute(): op.execute('UPDATE t SET col = 1').","If you have a SQLAlchemy expression, compile it to a string first: op.execute(str(my_expr.compile(dialect=bind.dialect))).","Keep data-migration execute() calls authored manually as raw SQL strings rather than expressions."],"exampleFix":"# before\nfrom sqlalchemy import text\nop.execute(text('UPDATE accounts SET active = true'))\n\n# after (plain string, autogenerate-safe)\nop.execute('UPDATE accounts SET active = true')","handlingStrategy":"type-guard","validationCode":"def execute_for_autogenerate(op, sql):\n    if not isinstance(sql, str):\n        sql = str(sql.compile(compile_kwargs={'literal_binds': True}))\n    op.execute(sql)","typeGuard":"def is_plain_sql_string(value) -> bool:\n    return isinstance(value, str)","tryCatchPattern":null,"preventionTips":["Always pass plain SQL strings to op.execute() in migrations that may be autogenerated.","Compile SQLAlchemy expressions to strings with literal_binds before passing to op.execute().","Keep data-migration execute() calls as authored raw SQL, not expression objects."],"tags":["autogenerate","op-execute","rendering","sql-expression"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}