buk*_*zor 140 python sqlalchemy
我真的希望能够为我的应用程序打印出有效的SQL,包括值,而不是绑定参数,但是在SQLAlchemy中如何做到这一点并不明显(按照设计,我很确定).
有没有人以一般方式解决这个问题?
zzz*_*eek 142
在绝大多数情况下,SQLAlchemy语句或查询的"字符串化"非常简单:
print str(statement)
Run Code Online (Sandbox Code Playgroud)
这既适用于ORM Query,也适用于任何select()或其他声明.
注意:sqlalchemy文档中正在维护以下详细解答.
要将语句编译为特定的方言或引擎,如果语句本身尚未绑定到语句本身,则可以将其传递给compile():
print statement.compile(someengine)
Run Code Online (Sandbox Code Playgroud)
或没有引擎:
from sqlalchemy.dialects import postgresql
print statement.compile(dialect=postgresql.dialect())
Run Code Online (Sandbox Code Playgroud)
给定一个ORM Query对象时,为了获得该compile()方法,我们只需要首先访问.statement访问器:
statement = query.statement
print statement.compile(someengine)
Run Code Online (Sandbox Code Playgroud)
关于绑定参数将被"内联"到最终字符串中的原始规定,这里的挑战是SQLAlchemy通常不会对此进行任务,因为这是由Python DBAPI适当处理的,更不用说绕过绑定参数了可能是现代Web应用程序中利用最广泛的安全漏洞.SQLAlchemy在某些情况下执行此字符串化的能力有限,例如发出DDL.为了访问此功能,可以使用'literal_binds'标志,传递给compile_kwargs:
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})
Run Code Online (Sandbox Code Playgroud)
上述方法有一些注意事项,它只支持基本类型,例如int和字符串,而且如果bindparam
没有直接使用预设值,它也将无法对其进行字符串化.
要支持不支持的类型的内联文字呈现,请为TypeDecorator包含TypeDecorator.process_literal_param方法的目标类型
实现a :
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})
)
Run Code Online (Sandbox Code Playgroud)
产生如下输出:
SELECT mytable.x
FROM mytable
WHERE mytable.x > my_fancy_formatting(5)
Run Code Online (Sandbox Code Playgroud)
buk*_*zor 63
这适用于python 2和3,并且比以前更清晰,但需要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
Run Code Online (Sandbox Code Playgroud)
演示:
# coding: UTF-8
from datetime import datetime
from decimal import Decimal
from literalquery import literalquery
def test():
from sqlalchemy.sql import table, column, select
mytable = table('mytable', column('mycol'))
values = (
5,
u'snowman: ?',
b'UTF-8 snowman: \xe2\x98\x83',
datetime.now(),
Decimal('3.14159'),
10 ** 20, # a long integer
)
statement = select([mytable]).where(mytable.c.mycol.in_(values)).limit(1)
print(literalquery(statement))
if __name__ == '__main__':
test()
Run Code Online (Sandbox Code Playgroud)
给出了这个输出:(在python 2.7和3.4中测试过)
SELECT mytable.mycol
FROM mytable
WHERE mytable.mycol IN (5, 'snowman: ?', 'UTF-8 snowman: ?',
'2015-06-24 18:09:29.042517', 3.14159, 100000000000000000000)
LIMIT 1
Run Code Online (Sandbox Code Playgroud)
Ved*_*ego 32
鉴于您想要的只在调试时才有意义,您可以启动SQLAlchemy echo=True,以记录所有SQL查询.例如:
engine = create_engine(
"mysql://scott:tiger@hostname/dbname",
encoding="latin1",
echo=True,
)
Run Code Online (Sandbox Code Playgroud)
这也可以仅针对单个请求进行修改:
echo=False- 如果True,引擎会将所有语句repr()及其参数列表记录到引擎记录器中,默认为sys.stdout.所述echo的属性Engine可以在任何时间进行修改,以打开记录和关闭.如果设置为字符串"debug",结果行也将打印到标准输出.此标志最终控制Python记录器; 有关如何直接配置日志记录的信息,请参阅配置日志记录.
如果与Flask一起使用,您只需设置即可
app.config["SQLALCHEMY_ECHO"] = True
Run Code Online (Sandbox Code Playgroud)
得到相同的行为.
aks*_*pal 19
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}))
Run Code Online (Sandbox Code Playgroud)
结果:
SELECT * FROM users WHERE users.name BETWEEN 'm' AND 'z'
Run Code Online (Sandbox Code Playgroud)
jma*_*son 13
因此,在@zzzeek对@ bukzor代码的评论的基础上,我想出了这个,以便轻松获得"非常可打印"的查询:
def prettyprintable(statement, dialect=None, reindent=True):
"""Generate an SQL expression string with bound parameters rendered inline
for the given SQLAlchemy statement. The function can also receive a
`sqlalchemy.orm.Query` object instead of statement.
can
WARNING: Should only be used for debugging. Inlining parameters is not
safe when handling user created data.
"""
import sqlparse
import sqlalchemy.orm
if isinstance(statement, sqlalchemy.orm.Query):
if dialect is None:
dialect = statement.session.get_bind().dialect
statement = statement.statement
compiled = statement.compile(dialect=dialect,
compile_kwargs={'literal_binds': True})
return sqlparse.format(str(compiled), reindent=reindent)
Run Code Online (Sandbox Code Playgroud)
我个人很难读取没有缩进的代码,所以我习惯于sqlparse重新编写SQL.它可以安装pip install sqlparse.
vvl*_*rov 10
此代码基于@bukzor 现有的精彩答案.我刚刚将自定义渲染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)
Run Code Online (Sandbox Code Playgroud)
要使用 Python 日志记录而不是标志来记录 SQL 查询
echo=True:Run Code Online (Sandbox Code Playgroud)import logging logging.basicConfig() logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
根据文档。
我想指出的是,以上给出的解决方案不适用于非平凡的查询。我遇到的一个问题是更复杂的类型,例如导致问题的pgsql ARRAY。我确实找到了一个对我来说甚至可以与pgsql ARRAY一起使用的解决方案:
借用:https : //gist.github.com/gsakkis/4572159
链接的代码似乎基于旧版本的SQLAlchemy。您会收到一条错误消息,指出_mapper_zero_or_none属性不存在。这是一个更新的版本,将与较新的版本一起使用,您只需将_mapper_zero_or_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)
Run Code Online (Sandbox Code Playgroud)
已测试到两个级别的嵌套数组。
| 归档时间: |
|
| 查看次数: |
79600 次 |
| 最近记录: |