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()
Asked By: shalama

||

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
Answered By: python_user
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.