7 ответов:
в подавляющем большинстве случаев "строкование" оператора SQLAlchemy или запроса так же просто, как:
print str(statement)это относится как к ORM
Queryкак и любойselect()или другое заявление.Примечание: следующий подробный ответ сохраняется на документация по sqlalchemy.
чтобы получить инструкцию, скомпилированную на определенном диалекте или движке, если сама инструкция еще не создана привязанный к одному вы можете передать это в compile ():
print statement.compile(someengine)или без двигателя:
from sqlalchemy.dialects import postgresql print statement.compile(dialect=postgresql.dialect())когда дается ОРМ
Queryобъект, чтобы добраться доcompile()метод нам нужен только доступ к .заявление accessor first:statement = query.statement print statement.compile(someengine)что касается исходного условия, что связанные параметры должны быть "встроены" в конечную строку, проблема здесь заключается в том, что SQLAlchemy обычно не задается этим, поскольку это обрабатывается соответствующим образом Python DBAPI, не говоря уже о том, что обход связанных параметров, вероятно, является наиболее широко используемыми дырами безопасности в современных веб-приложениях. 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})приведенный выше подход имеет предостережения, что он поддерживается только для basic типы, например, целых чисел и строк, а кроме того, если
bindparamбез заданного значения используется напрямую, он не сможет и это тоже надо стринговать.для поддержки встроенного литерального рендеринга для типов, не поддерживаемых, реализовать а
TypeDecoratorдля целевого типа, который включает в себяTypeDecorator.process_literal_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)
это работает в 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демо:
# 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()дает этот вывод: (проверено в 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
Итак, основываясь на комментариях @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)мне лично трудно читать код, который не имеет отступа, поэтому я использовал
sqlparseв reindent в SQL. Он может быть установлен сpip install sqlparse.
этот код основан на блестящей существующий ответ от @bukzor. Я только что добавил пользовательский рендер для
datetime.datetimeвведите в OracleTO_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)
можно использовать compile метод для этой цели. Из docs:
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'
Я хотел бы отметить, что приведенные выше решения не "просто работают" с нетривиальными запросами. Одна проблема, с которой я столкнулся, была более сложными типами, такими как массивы pgsql, вызывающие проблемы. Я нашел решение, которое для меня просто работало даже с массивами pgsql:
заимствовано из: 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)проверено на двух уровнях вложенных массивов.
учитывая, что то, что вы хотите, имеет смысл только при отладке, вы можете запустить SQLAlchemy с
echo=True, для регистрации всех запросов SQL. Например:engine = create_engine( "mysql://scott:tiger@hostname/dbname", encoding="latin1", echo=True, )это также может быть изменено только для одного запроса:
echo=False– ЕслиTrue, двигатель будет регистрировать все заявления, а такжеrepr()из их списков параметров в регистратор двигателей, который по умолчаниюsys.stdout. Элемент наEngineможет быть изменен в любое время, чтобы включить ведение журнала включаю и выключаю. Если строка"debug", строки результата будут напечатаны к стандартному выходу также. Этот флаг в конечном счете управляет регистратором Python; см. Настройка Ведения Журнала для получения информации о настройке журналирования.источник: Конфигурация Двигателя SQLAlchemy
если используется с колбой, вы можете просто установить
app.config["SQLALCHEMY_ECHO"] = Trueчтобы получить такое же поведение.
Comments