Reputation: 400
How can I parse clauses, given as parameters in a filter method?
Running of
filters_clause = Record.start>='2017-07-17'
print(filters_clause)
gives a string
records.start >= :start_1
It's necessary a real value instead of :start_1 and that value must have been passed by process_bind_param function. Which method I have to use to get a string like that: records.start >= '1500321600.0'?
#!/usr/bin/env python
# coding=utf-8
from __future__ import ( division, absolute_import,
print_function, unicode_literals )
import time
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker, attributes
from sqlalchemy.types import TypeDecorator
from sqlalchemy import create_engine, MetaData, Column, Integer, String
Base = declarative_base()
class EpochTime(TypeDecorator):
impl = Integer
def process_bind_param(self, value, dialect):
if isinstance(value, unicode):
if value.isdigit():
return value
if len(value) == 10:
value = time.mktime(time.strptime(value, "%Y-%m-%d"))
elif len(value) == 13:
value = time.mktime(time.strptime(value, "%Y-%m-%d %H"))
elif len(value) == 16:
value = time.mktime(time.strptime(value, "%Y-%m-%d %H:%M"))
return value
def process_result_value(self, value, dialect):
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(value))
class Record(Base):
__tablename__ = 'records'
id = Column(Integer, primary_key=True)
name = Column(String)
start = Column(EpochTime)
engine = create_engine('sqlite://')
session = scoped_session(sessionmaker())
session.configure(bind=engine)
Base.metadata.create_all(engine)
record = Record(name = 'Record 1', start = '2017-07-16')
session.add(record)
record = Record(name = 'Record 2', start = '2017-07-17')
session.add(record)
record = Record(name = 'Record 3', start = '2017-07-18')
session.add(record)
session.commit()
filters_clause = Record.start>='2017-07-17'
s = session.query(Record).filter(filters_clause)
res = s.all()
names = [i.name for i in Record.__table__.c]
rows = [[row.__dict__.get(i) for i in names] for row in res]
for row in rows:
print(row)
print(0, s)
print(1, filters_clause)
Upvotes: 0
Views: 1432
Reputation: 20548
I think what you want is literal_binds
:
print(filters_clause.compile(engine, compile_kwargs={"literal_binds": True}))
# records.start >= 1500274800.0
Be wary of SQL injection if you use literal_binds
, though.
Upvotes: 1
Reputation: 15449
I don't think that you can achieve this simply with vanilla sqlalchemy. You can try using a custom function, like:
import re
def represent_filter(expression):
expression = expression.compile()
params = expression.params
prefixed_names = [':' + param for param in params]
pattern = re.compile('(%s)' % '|'.join(prefixed_names))
def substitute_param_value(match):
return params[match.group()[1:]]
return pattern.sub(substitute_param_value, expression.string)
Then use it calling represent_filter(filters_clause)
and it will return records.start >= 2017-07-17
.
You could force represent_filter function to quote substituted value, using something like repr(str(params[match.group()[1:]]))
instead of params[match.group()[1:]]
.
An other approach would be to traverse given expression (in your case filters_clause
is a BinaryExpression
, there are unary expressions too), check left and right sides and build your own representation. If you only want to recover the value (the date which you provided to filter) it is in filters_clause.right.effective_value
.
Upvotes: 0