바인드 매개 변수가 아닌 값을 포함하여 응용 프로그램에 유효한 SQL을 실제로 인쇄하고 싶지만 SQLAlchemy 에서이 작업을 수행하는 방법은 확실하지 않습니다 (설계 상 상당히 확실합니다).
누구 든지이 문제를 일반적인 방식으로 해결 했습니까?
바인드 매개 변수가 아닌 값을 포함하여 응용 프로그램에 유효한 SQL을 실제로 인쇄하고 싶지만 SQLAlchemy 에서이 작업을 수행하는 방법은 확실하지 않습니다 (설계 상 상당히 확실합니다).
누구 든지이 문제를 일반적인 방식으로 해결 했습니까?
답변:
대부분의 경우 SQLAlchemy 문 또는 쿼리의 "문자열"은 다음과 같이 간단합니다.
print str(statement)
이것은 ORM Query
뿐만 아니라 select()
다른 진술 에도 적용됩니다 .
참고 : 다음 자세한 답변은 sqlalchemy 설명서 에서 유지 관리됩니다 .
명령문을 특정 방언 또는 엔진에 컴파일 된 것으로 가져 오려면 명령문 자체가 아직 바인딩되지 않은 경우이를 compile () 에 전달할 수 있습니다 .
print statement.compile(someengine)
또는 엔진이없는 경우 :
from sqlalchemy.dialects import postgresql
print statement.compile(dialect=postgresql.dialect())
ORM Query
객체가 주어지면 compile()
메소드 를 얻으려면 먼저 .statement 접근 자만 액세스하면 됩니다 .
statement = query.statement
print statement.compile(someengine)
바운드 매개 변수가 최종 문자열에 "인라인"되어야한다는 원래 규정과 관련하여 여기서 해결해야 할 과제는 SQLAlchemy가 일반적으로이 작업을 수행하지 않는 것입니다. 아마도 현대 웹 애플리케이션에서 가장 널리 이용되는 보안 허점 일 것입니다. 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})
위의 접근 방식은 int 및 string과 같은 기본 유형에서만 지원된다는 경고가 있습니다. 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)
query.prettyprint()
. 큰 쿼리로 엄청난 디버깅 문제를 해결합니다.
@compiles
예쁜 인쇄 시스템을 구현하기 위해 많은 타사 패키지에 대한 충분한 후크 (예 : cursor_execute 이벤트, 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
원하는 경우 디버깅 할 때만 의미가 echo=True
있으므로 SQLAlchemy를 사용 하여 모든 SQL 쿼리를 기록 할 수 있습니다. 예를 들면 다음과 같습니다.
engine = create_engine(
"mysql://scott:tiger@hostname/dbname",
encoding="latin1",
echo=True,
)
단일 요청에 대해서만 수정할 수도 있습니다.
echo=False
–이면True
엔진은 모든 명령문과repr()
매개 변수 목록을 엔진 로거에 기본값으로 기록합니다sys.stdout
. 의echo
속성은Engine
언제든지 로깅을 켜거나 끄도록 수정할 수 있습니다. string으로 설정하면"debug"
결과 행도 표준 출력으로 인쇄됩니다. 이 플래그는 궁극적으로 Python 로거를 제어합니다. 로깅을 직접 구성하는 방법에 대한 정보는 로깅 구성을 참조하십시오 .소스 : SQLAlchemy 엔진 구성
플라스크와 함께 사용하면 간단하게 설정할 수 있습니다
app.config["SQLALCHEMY_ECHO"] = True
같은 행동을 취합니다.
flask-sqlalchemy
이 사용자에게는 허용되는 답변이어야합니다.
이 목적으로 컴파일 방법을 사용할 수 있습니다 . 로부터 문서 :
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'
문서에서 경고 :
웹 양식이나 다른 사용자 입력 응용 프로그램과 같은 신뢰할 수없는 입력에서 수신 한 문자열 내용에는이 기술을 사용하지 마십시오. Python 값을 직접 SQL 문자열 값으로 강제 변환하는 SQLAlchemy의 기능은 신뢰할 수없는 입력에 대해 안전하지 않으며 전달되는 데이터 유형의 유효성을 검증하지 않습니다. 관계형 데이터베이스에 대해 비 DDL SQL 문을 프로그래밍 방식으로 호출 할 때는 항상 바인딩 된 매개 변수를 사용하십시오.
따라서 @bukzor의 코드에 대한 @zzzeek의 의견을 바탕으로 "꽤 인쇄 가능한"질의를 쉽게 얻기 위해 이것을 생각해 냈습니다.
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
을 겪어 SQL을 다시 들여 쓰는 데 사용 했습니다. 로 설치할 수 있습니다 pip install sqlparse
.
datatime.now()
python 3 + sqlalchemy 1.0을 사용할 때의 값을 제외한 모든 값이 작동합니다 . 해당 기능을 사용하려면 사용자 정의 TypeDecorator를 만드는 방법에 대한 @zzzeek의 조언을 따라야합니다.
이 코드는 @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)
return "%s" % value
대신 return repr(value)
플로트, INT에, 긴 섹션 파이썬으로 걷고 출력 때문에 22L
대신의22
"STR_TO_DATE('%s','%%Y-%%m-%%d %%H:%%M:%%S')" % value.strftime("%Y-%m-%d %H:%M:%S")
mysql에서
위에서 언급 한 솔루션이 사소한 쿼리로만 작동하지는 않습니다. 내가 만난 한 가지 문제는 pgsql ARRAY와 같은 더 복잡한 유형이었습니다. 나는 나를 위해 pgsql ARRAY에서도 작동하는 해결책을 찾았습니다.
빌린 곳 : https://gist.github.com/gsakkis/4572159
연결된 코드는 이전 버전의 SQLAlchemy를 기반으로하는 것 같습니다. _mapper_zero_or_none 속성이 존재하지 않는다는 오류가 발생합니다. 다음은 최신 버전에서 작동하는 업데이트 된 버전입니다. _mapper_zero_or_none을 바인드로 바꾸면됩니다. 또한 이것은 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)
두 수준의 중첩 배열로 테스트되었습니다.
from file import render_query; print(render_query(query))
sqlalchemy.engine
로그를 활용하여 덜 취약한 솔루션을 구축 할 수 있습니다. 쿼리 및 바인드 매개 변수를 기록하므로 바인드 자리 표시자를 쉽게 구성된 SQL 쿼리 문자열의 값으로 바꾸면됩니다.