J'aimerais vraiment pouvoir imprimer du code SQL valide pour mon application, y compris des valeurs, plutôt que des paramètres de liaison, mais il n'est pas évident de savoir comment procéder dans SQLAlchemy (à dessein, j'en suis à peu près sûr).
Quelqu'un at-il résolu ce problème de manière générale?
Cela fonctionne dans python 2 et 3 et est un peu plus propre qu'avant, mais requiert 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
Démo:
# 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 __== '__main__':
test()
Donne cette sortie: (testé dans python 2.7 et 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
Dans la grande majorité des cas, la "codification" d'une instruction ou d'une requête SQLAlchemy est aussi simple que:
print str(statement)
Ceci s’applique à la fois à un ORM Query
ainsi qu’à toute instruction select()
ou autre.
Remarque : la réponse détaillée suivante est conservée sur le documentation de sqlalchemy .
Pour obtenir l'instruction telle que compilée dans un dialecte ou un moteur spécifique, si l'instruction elle-même n'est pas déjà liée à un dialecte, vous pouvez la transmettre à compile () :
print statement.compile(someengine)
ou sans moteur:
from sqlalchemy.dialects import postgresql
print statement.compile(dialect=postgresql.dialect())
Lorsqu'un objet ORM Query
est attribué, pour accéder à la méthode compile()
, nous avons uniquement besoin d'accéder à l'accesseur . Statement :
statement = query.statement
print statement.compile(someengine)
en ce qui concerne la stipulation initiale voulant que les paramètres liés soient "insérés" dans la chaîne finale, le problème est que SQLAlchemy n’est normalement pas chargé de cela, car cela est géré de manière appropriée par le Python DBAPI, sans parler des paramètres liés, est probablement la faille de sécurité la plus largement exploitée dans les applications Web modernes. SQLAlchemy a une capacité limitée à faire cette chaîne de caractères dans certaines circonstances telles que l’émission de DDL. Pour accéder à cette fonctionnalité, vous pouvez utiliser Le drapeau de literal_binds, passé à 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})
l'approche ci-dessus présente les mises en garde selon lesquelles il n'est pris en charge que pour les types de base, tels que ints et strings, et de plus, si un bindparam
sans valeur prédéfinie est utilisé directement, il ne pourra pas en déduire que non plus.
Pour prendre en charge le rendu littéral en ligne pour les types non pris en charge, implémentez un TypeDecorator
pour le type de cible, qui inclut une méthode 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})
)
produire une sortie comme:
SELECT mytable.x
FROM mytable
WHERE mytable.x > my_fancy_formatting(5)
Étant donné que ce que vous voulez n'a de sens que lors du débogage, vous pouvez démarrer SQLAlchemy avec echo=True
Pour consigner toutes les requêtes SQL. Par exemple:
engine = create_engine(
"mysql://scott:tiger@hostname/dbname",
encoding="latin1",
echo=True,
)
Cela peut également être modifié pour une seule demande:
echo=False
- siTrue
, le moteur enregistre toutes les instructions ainsi qu'unrepr()
de leurs listes de paramètres dans le consignateur du moteur, dont la valeur par défaut estsys.stdout
. L'attributecho
deEngine
peut être modifié à tout moment pour activer et désactiver la journalisation. Si défini sur la chaîne"debug"
, Les lignes de résultat seront également imprimées sur la sortie standard. Cet indicateur contrôle en fin de compte un Python; voir Configuration de la journalisation ) pour obtenir des informations sur la configuration de la journalisation directement.
Si utilisé avec Flask, vous pouvez simplement définir
app.config["SQLALCHEMY_ECHO"] = True
avoir le même comportement.
Nous pouvons utiliser la méthode compiler à cette fin. De la 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}))
Résultat:
SELECT * FROM users WHERE users.name BETWEEN 'm' AND 'z'
Donc, en me basant sur les commentaires de @ zzzeek sur le code de @ bukzor, je suis parvenu à ceci pour obtenir facilement une requête "jolie-imprimable":
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)
Personnellement, j'ai du mal à lire le code qui n'est pas mis en retrait et j'ai donc utilisé sqlparse
pour réindentir le code SQL. Il peut être installé avec pip install sqlparse
.
Ce code est basé sur brillant réponse existante de @bukzor. Je viens d'ajouter un rendu personnalisé pour le type datetime.datetime
Dans la TO_DATE()
d'Oracle.
N'hésitez pas à mettre à jour le code correspondant à votre base de données:
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)
Je tiens à souligner que les solutions données ci-dessus ne "fonctionnent pas" avec des requêtes non triviales. Un problème que j'ai rencontré était des types plus compliqués, tels que pgsql ARRAYs provoquant des problèmes. J'ai trouvé une solution qui, pour moi, ne fonctionnait que même avec les tableaux de pgsql:
emprunté à: https://Gist.github.com/gsakkis/4572159
Le code lié semble être basé sur une version plus ancienne de SQLAlchemy. Vous obtiendrez une erreur indiquant que l'attribut _mapper_zero_or_none n'existe pas. Voici une version mise à jour qui fonctionnera avec une version plus récente, il vous suffit de remplacer _mapper_zero_or_none par bind. De plus, cela supporte les tableaux 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)
Testé sur deux niveaux de tableaux imbriqués.