sqlalchemy filter on date + offset
Question:
what I try to do is to create a query that finds all records where a date is greater than today – days_before_activation.
For this I use a @hybrid_property that shows the correct start_day (today – days_before_activation).
The issue is, that timedelta does not work in filter queries, at least with sqllite.
Error:
E TypeError: unsupported type for timedelta days component:
InstrumentedAttribute
For this I created an additional column expression (@start.expression). To add the days using plain sql. Unfortunately it does not work when I use the cls.days_before_activation value as part of the expression. The result is always None. However, when I hardcode the integer value it does work. So it looks like I’m doing something wrong with using the value of the property as part of the expression.
Not Working:
@start.expression
def start(cls):
return func.date(datetime.today(), f'+{cls.days_before_activation} days')
Working:
@start.expression
def start(cls):
return func.date(datetime.today(), '+ 10 days')
Any help are much appreciated.
class DefPayoutOffer(db.Model):
__tablename__ = 'def_payout_option_offer'
id = db.Column(db.Integer, primary_key=True)
visual_text = db.Column(db.String(1000), unique=False, nullable=False)
days_before_activation = db.Column(db.Integer, nullable=True)
@hybrid_property
def start(self):
return datetime.today() - timedelta(days=self.days_before_activation)
@start.expression
def start(cls):
return func.date(datetime.today(), f'+{cls.days_before_activation} days')
Query:
DefPayoutOffer.query.filter(DefPayoutOffer.start > created_date).all()
UPDATE:
After finding the solution with the help of python_user.
I had the challenge to make it work for different dialects.
For this I had to move away from the column expression approach to creating a expression.FunctionElement. Advantage is that it can be made dialect specific.
class StartCheck(expression.FunctionElement):
name = 'start_check'
inherit_cache = True
@compiles(StartCheck, 'otherDialect')
def compile(element, compiler, **kw):
return "foo"
@compiles(StartCheck, 'sqlite')
def compile(element, compiler, **kw):
return compiler.process(func.date('now', '+' + expression.cast(list(element.clauses)[0], Unicode) + ' days'))
DefPayoutOffer.query.filter(StartCheck(DefPayoutOffer.days_before_activation) >= date.today()).first()
Answers:
You have to use expression.cast
with types.Unicode
to get what you want.
In addition to what you have, you need these imports and then change start.expression
like so
from sqlalchemy.types import Unicode
from sqlalchemy.sql import expression
@start.expression
def start(cls):
return func.date('now', '+' + expression.cast(cls.days_before_activation, Unicode) + ' days')
Also as a side note, you can use date('now')
to get the current date, you do not have to use datetime.today()
like you have shown.
PS: 999 rep, enjoy 1k+ with that upvote
Edit: To select based on dialect, sqlite / postgres
If you have access to engine
that is used by the session you can use this
@start.expression
def start(cls):
if engine.dialect.name == 'sqlite':
return func.date('now', '+' + cast(cls.days_before_activation, Unicode) + ' days')
elif engine.dialect.name == 'postgresql':
return func.current_date() + cls.days_before_activation
what I try to do is to create a query that finds all records where a date is greater than today – days_before_activation.
For this I use a @hybrid_property that shows the correct start_day (today – days_before_activation).
The issue is, that timedelta does not work in filter queries, at least with sqllite.
Error:
E TypeError: unsupported type for timedelta days component:
InstrumentedAttribute
For this I created an additional column expression (@start.expression). To add the days using plain sql. Unfortunately it does not work when I use the cls.days_before_activation value as part of the expression. The result is always None. However, when I hardcode the integer value it does work. So it looks like I’m doing something wrong with using the value of the property as part of the expression.
Not Working:
@start.expression
def start(cls):
return func.date(datetime.today(), f'+{cls.days_before_activation} days')
Working:
@start.expression
def start(cls):
return func.date(datetime.today(), '+ 10 days')
Any help are much appreciated.
class DefPayoutOffer(db.Model):
__tablename__ = 'def_payout_option_offer'
id = db.Column(db.Integer, primary_key=True)
visual_text = db.Column(db.String(1000), unique=False, nullable=False)
days_before_activation = db.Column(db.Integer, nullable=True)
@hybrid_property
def start(self):
return datetime.today() - timedelta(days=self.days_before_activation)
@start.expression
def start(cls):
return func.date(datetime.today(), f'+{cls.days_before_activation} days')
Query:
DefPayoutOffer.query.filter(DefPayoutOffer.start > created_date).all()
UPDATE:
After finding the solution with the help of python_user.
I had the challenge to make it work for different dialects.
For this I had to move away from the column expression approach to creating a expression.FunctionElement. Advantage is that it can be made dialect specific.
class StartCheck(expression.FunctionElement):
name = 'start_check'
inherit_cache = True
@compiles(StartCheck, 'otherDialect')
def compile(element, compiler, **kw):
return "foo"
@compiles(StartCheck, 'sqlite')
def compile(element, compiler, **kw):
return compiler.process(func.date('now', '+' + expression.cast(list(element.clauses)[0], Unicode) + ' days'))
DefPayoutOffer.query.filter(StartCheck(DefPayoutOffer.days_before_activation) >= date.today()).first()
You have to use expression.cast
with types.Unicode
to get what you want.
In addition to what you have, you need these imports and then change start.expression
like so
from sqlalchemy.types import Unicode
from sqlalchemy.sql import expression
@start.expression
def start(cls):
return func.date('now', '+' + expression.cast(cls.days_before_activation, Unicode) + ' days')
Also as a side note, you can use date('now')
to get the current date, you do not have to use datetime.today()
like you have shown.
PS: 999 rep, enjoy 1k+ with that upvote
Edit: To select based on dialect, sqlite / postgres
If you have access to engine
that is used by the session you can use this
@start.expression
def start(cls):
if engine.dialect.name == 'sqlite':
return func.date('now', '+' + cast(cls.days_before_activation, Unicode) + ' days')
elif engine.dialect.name == 'postgresql':
return func.current_date() + cls.days_before_activation