Python SQLAlchemy:打印实际查询

Python SQLAlchemy:打印实际查询,python,sqlalchemy,Python,Sqlalchemy,我真的希望能够为我的应用程序打印出有效的SQL,包括值,而不是绑定参数,但在SQLAlchemy中如何做到这一点并不明显(通过设计,我相当确定) 有人用一般方法解决了这个问题吗?这在Python2和Python3中工作,比以前更简洁,但需要SA>=1.0 from sqlalchemy.engine.default import DefaultDialect from sqlalchemy.sql.sqltypes import String, DateTime, NullType # pyt

我真的希望能够为我的应用程序打印出有效的SQL,包括值,而不是绑定参数,但在SQLAlchemy中如何做到这一点并不明显(通过设计,我相当确定)


有人用一般方法解决了这个问题吗?

这在Python2和Python3中工作,比以前更简洁,但需要SA>=1.0

from sqlalchemy.engine.default import DefaultDialect
from sqlalchemy.sql.sqltypes import String, DateTime, NullType

# python2/3 compatible.
PY3 = str is not bytes
text = str if PY3 else unicode
int_type = int if PY3 else (int, long)
str_type = str if PY3 else (str, unicode)


class StringLiteral(String):
    """Teach SA how to literalize various things."""
    def literal_processor(self, dialect):
        super_processor = super(StringLiteral, self).literal_processor(dialect)

        def process(value):
            if isinstance(value, int_type):
                return text(value)
            if not isinstance(value, str_type):
                value = text(value)
            result = super_processor(value)
            if isinstance(result, bytes):
                result = result.decode(dialect.encoding)
            return result
        return process


class LiteralDialect(DefaultDialect):
    colspecs = {
        # prevent various encoding explosions
        String: StringLiteral,
        # teach SA about how to literalize a datetime
        DateTime: StringLiteral,
        # don't format py2 long integers to NULL
        NullType: StringLiteral,
    }


def literalquery(statement):
    """NOTE: This is entirely insecure. DO NOT execute the resulting strings."""
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        statement = statement.statement
    return statement.compile(
        dialect=LiteralDialect(),
        compile_kwargs={'literal_binds': True},
    ).string
演示:

给出以下输出:(在Python2.7和3.4中测试)


这段代码基于@bukzor的brilliant。我刚刚为
datetime添加了自定义渲染。datetime
输入到Oracle的
TO_DATE()

请随时更新代码以适合您的数据库:

import decimal
import datetime

def printquery(statement, bind=None):
    """
    print a query, with values filled in
    for debugging purposes *only*
    for security, you should always separate queries from their values
    please also note that this function is quite slow
    """
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        if bind is None:
            bind = statement.session.get_bind(
                    statement._mapper_zero_or_none()
            )
        statement = statement.statement
    elif bind is None:
        bind = statement.bind 

    dialect = bind.dialect
    compiler = statement._compiler(dialect)
    class LiteralCompiler(compiler.__class__):
        def visit_bindparam(
                self, bindparam, within_columns_clause=False, 
                literal_binds=False, **kwargs
        ):
            return super(LiteralCompiler, self).render_literal_bindparam(
                    bindparam, within_columns_clause=within_columns_clause,
                    literal_binds=literal_binds, **kwargs
            )
        def render_literal_value(self, value, type_):
            """Render the value of a bind parameter as a quoted literal.

            This is used for statement sections that do not accept bind paramters
            on the target driver/database.

            This should be implemented by subclasses using the quoting services
            of the DBAPI.

            """
            if isinstance(value, basestring):
                value = value.replace("'", "''")
                return "'%s'" % value
            elif value is None:
                return "NULL"
            elif isinstance(value, (float, int, long)):
                return repr(value)
            elif isinstance(value, decimal.Decimal):
                return str(value)
            elif isinstance(value, datetime.datetime):
                return "TO_DATE('%s','YYYY-MM-DD HH24:MI:SS')" % value.strftime("%Y-%m-%d %H:%M:%S")

            else:
                raise NotImplementedError(
                            "Don't know how to literal-quote value %r" % value)            

    compiler = LiteralCompiler(dialect, statement)
    print compiler.process(statement)

在绝大多数情况下,SQLAlchemy语句或查询的“字符串化”非常简单:

print(str(statement))
这既适用于ORM
查询
,也适用于任何
select()
或其他语句

注意:以下详细答案保存在网站上

要将语句编译为特定方言或引擎,如果语句本身尚未绑定到某个方言或引擎,则可以将其传递到:

或没有发动机:

from sqlalchemy.dialects import postgresql
print(statement.compile(dialect=postgresql.dialect()))
当给定ORM
Query
对象时,为了获得
compile()
方法,我们只需要首先访问访问器:

statement = query.statement
print(statement.compile(someengine))
关于绑定参数要“内联”到最终字符串的原始规定,这里的挑战是SQLAlchemy通常不负责此任务,因为这是由Python DBAPI适当处理的,更不用说绕过绑定参数可能是现代web应用程序中最广泛利用的安全漏洞。SQLAlchemy在某些情况下(例如发出DDL的情况)执行这种字符串化的能力有限。为了访问此功能,可以使用传递给
compile\u kwargs
的“literal\u binds”标志:

from sqlalchemy.sql import table, column, select

t = table('t', column('x'))

s = select([t]).where(t.c.x == 5)

print(s.compile(compile_kwargs={"literal_binds": True}))
上述方法需要注意的是,它只支持基本的 类型,例如int和string,而且如果a
bindparam
如果不直接使用预设值,它将无法 也要把它严格化

要支持不支持的类型的内联文字呈现,请实现 目标类型的
TypeDecorator
,其中包括
TypeDecorator.process\u literal\u param
方法:

from sqlalchemy import TypeDecorator, Integer


class MyFancyType(TypeDecorator):
    impl = Integer

    def process_literal_param(self, value, dialect):
        return "my_fancy_formatting(%s)" % value

from sqlalchemy import Table, Column, MetaData

tab = Table('mytable', MetaData(), Column('x', MyFancyType()))

print(
    tab.select().where(tab.c.x > 5).compile(
        compile_kwargs={"literal_binds": True})
)
产生如下产出:

SELECT mytable.x
FROM mytable
WHERE mytable.x > my_fancy_formatting(5)

因此,在@zzzeek对@bukzor代码的评论的基础上,我提出了一个简单的“可打印”查询:


我个人很难阅读没有缩进的代码,所以我使用了
sqlparse
来重新修改SQL。它可以与pip install sqlparse一起安装。我想指出的是,上面给出的解决方案并不“只适用于”非平凡的查询。我遇到的一个问题是更复杂的类型,例如导致问题的pgsql数组。我确实找到了一个解决方案,对我来说,它甚至可以用于pgsql阵列:

借用自:

链接的代码似乎基于SQLAlchemy的旧版本。您将得到一个错误,指出属性_mapper_zero_或_none不存在。这里有一个更新的版本,可以与更新的版本一起使用,您只需将_mapper_zero_或_none替换为bind即可。此外,它还支持pgsql阵列:

# adapted from:
# https://gist.github.com/gsakkis/4572159
from datetime import date, timedelta
from datetime import datetime

from sqlalchemy.orm import Query


try:
    basestring
except NameError:
    basestring = str


def render_query(statement, dialect=None):
    """
    Generate an SQL expression string with bound parameters rendered inline
    for the given SQLAlchemy statement.
    WARNING: This method of escaping is insecure, incomplete, and for debugging
    purposes only. Executing SQL statements with inline-rendered user values is
    extremely insecure.
    Based on http://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query
    """
    if isinstance(statement, Query):
        if dialect is None:
            dialect = statement.session.bind.dialect
        statement = statement.statement
    elif dialect is None:
        dialect = statement.bind.dialect

    class LiteralCompiler(dialect.statement_compiler):

        def visit_bindparam(self, bindparam, within_columns_clause=False,
                            literal_binds=False, **kwargs):
            return self.render_literal_value(bindparam.value, bindparam.type)

        def render_array_value(self, val, item_type):
            if isinstance(val, list):
                return "{%s}" % ",".join([self.render_array_value(x, item_type) for x in val])
            return self.render_literal_value(val, item_type)

        def render_literal_value(self, value, type_):
            if isinstance(value, long):
                return str(value)
            elif isinstance(value, (basestring, date, datetime, timedelta)):
                return "'%s'" % str(value).replace("'", "''")
            elif isinstance(value, list):
                return "'{%s}'" % (",".join([self.render_array_value(x, type_.item_type) for x in value]))
            return super(LiteralCompiler, self).render_literal_value(value, type_)

    return LiteralCompiler(dialect, statement).process(statement)
测试了两个级别的嵌套数组。

我们可以使用此方法。从:

结果:

SELECT * FROM users WHERE users.name BETWEEN 'm' AND 'z'
来自文档的警告:

切勿将此技术用于从不受信任的服务器接收的字符串内容 输入,例如来自web表单或其他用户输入应用程序的输入。 SQLAlchemy将Python值强制转换为直接SQL字符串的功能 值对于不受信任的输入不安全,并且不会验证 正在传递的数据类型。在以下情况下始终使用绑定参数: 对关系数据库以编程方式调用非DDL SQL语句 数据库


考虑到您想要的只有在调试时才有意义,您可以使用
echo=True
启动SQLAlchemy来记录所有SQL查询。例如:

engine = create_engine(
    "mysql://scott:tiger@hostname/dbname",
    encoding="latin1",
    echo=True,
)
也可以针对单个请求进行修改:

echo=False
–如果
True
,引擎将把所有语句及其参数列表的
repr()
记录到引擎记录器,该记录器默认为
sys.stdout
Engine
echo
属性可以随时修改以打开和关闭日志记录。如果设置为字符串
“debug”
,结果行也将打印到标准输出。该标志最终控制Python记录器;有关如何直接配置日志记录的信息,请参阅

资料来源:

如果与烧瓶一起使用,您只需设置

app.config["SQLALCHEMY_ECHO"] = True

获得相同的行为。

只是一个带有ORM查询和pygments的简单彩色示例

import sqlparse
from pygments import highlight
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexers import SqlLexer
from sqlalchemy import create_engine
from sqlalchemy.orm import Query

engine = create_engine("sqlite+pysqlite:///db.sqlite", echo=True, future=True)

def format_sql(query: Query):
    compiled = query.statement.compile(
         engine, compile_kwargs={"literal_binds": True})
    parsed = sqlparse.format(str(compiled), reindent=True, keyword_case='upper')
    print(highlight(parsed, SqlLexer(), TerminalFormatter()))
或者没有sqlparse的版本(没有sqlparse,输出中的新行更少)


我没有,但是您可以通过使用SQLAlchemy的
SQLAlchemy.engine
log来构建一个不那么脆弱的解决方案。它记录查询和绑定参数,您只需用现成构造的SQL查询字符串上的值替换绑定占位符。@Simon:使用记录器有两个问题:1)它只在语句执行时打印2)我仍然需要进行字符串替换,除非在这种情况下,我不知道绑定模板字符串的确切位置,我必须以某种方式从查询文本中解析它,使解决方案更加脆弱。新的URL似乎是为@zzzeek的FAQ提供的。我不明白为什么SA人员认为如此简单的操作如此困难是合理的。谢谢!render_literal_值对我来说效果很好。我唯一的更改是:
return“%s”%value
而不是float,int,long部分中的
return repr(value)
,因为Python将long输出为
22L
,而不是
22
,如果任何bindparam字符串值不能用ascii表示,则此配方(以及原始配方)将引发UnicodeDecodeError。我贴了一张
from sqlalchemy.sql import text
from sqlalchemy.dialects import postgresql

stmt = text("SELECT * FROM users WHERE users.name BETWEEN :x AND :y")
stmt = stmt.bindparams(x="m", y="z")

print(stmt.compile(dialect=postgresql.dialect(),compile_kwargs={"literal_binds": True}))
SELECT * FROM users WHERE users.name BETWEEN 'm' AND 'z'
engine = create_engine(
    "mysql://scott:tiger@hostname/dbname",
    encoding="latin1",
    echo=True,
)
app.config["SQLALCHEMY_ECHO"] = True
import sqlparse
from pygments import highlight
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexers import SqlLexer
from sqlalchemy import create_engine
from sqlalchemy.orm import Query

engine = create_engine("sqlite+pysqlite:///db.sqlite", echo=True, future=True)

def format_sql(query: Query):
    compiled = query.statement.compile(
         engine, compile_kwargs={"literal_binds": True})
    parsed = sqlparse.format(str(compiled), reindent=True, keyword_case='upper')
    print(highlight(parsed, SqlLexer(), TerminalFormatter()))
def format_sql(query: Query):
    compiled = query.statement.compile(
        engine, compile_kwargs={"literal_binds": True})
    print(highlight(str(compiled), SqlLexer(), TerminalFormatter()))