Reputation: 748
I have a SQLAlchemy model in my Flask app that looks something like this:
class MyModel(db.model):
_date_start = db.Column(db.Date, nullable=False, default=date.today)
_date_end = db.Column(db.Date, nullable=True, default=None)
@hybrid_property
def date_start(self) -> date:
return self._date_start
@hybrid_property
def date_end(self) -> date:
return self._date_end
@hybrid_property
def date_last_active(self) -> date:
if self.date_end:
return self.date_end
return date.today()
@hybrid_method
def contains(self, date: date) -> bool:
return (date >= self.date_start and date <= self.date_last_active)
@contains.expression
def contains(cls, date: date) -> bool:
return and_(date >= cls._date_start, date <= cls.date_last_active)
When I create an entry with a null date_end
, my contains
method fails to find that entry. This appears to be because of the date_last_active
hybrid property:
>>> entry = MyModel(_date_start=date(2022, 12, 1)
>>> # ... add and commit, then:
>>> MyModel.query.filter(MyModel.contains(date(2022, 12, 5)).count()
0
>>> MyModel.query.filter(date(2022, 12, 5) >= MyModel.date_start).count()
1
>>> MyModel.query.filter(date(2022, 12, 5) <= MyModel.date_last_active).count()
0
>>> MyModel.query.filter(date(2022, 12, 5) <= func.current_date()).count()
1
I'd guessed that the conditional is what's tripping it up, so I tried adding an expression with a case
statement, but I can't get it to work. Two expressions I've tried are:
# case attempt 1
@date_last_active.expression
def date_last_active(cls) -> date:
return case(
(cls._date_end.is_(None), func.current_date()),
(not_(cls._date_end.is_(None)), cls._date_end)
)
# Raises: NotImplementedError: Operator 'getitem' is not supported on this expression
# case attempt 2
@date_last_active.expression
def date_last_active(cls) -> date:
return case(
{cls._date_end.is_(None): func.current_date()},
value=cls._date_end,
else_=cls._date_end
)
# Still doesn't find the entry:
# >>> DailyStreak.query.filter(date(2022, 12, 5) <= DailyStreak.date_last_active).count()
# 0
I feel like I'm close, but I'm just not seeing where I need to go. Thanks for the help!
Upvotes: 0
Views: 48
Reputation: 9039
Expanding on what @IljaEverilä said this seems to work:
class MyModel(Base):
__tablename__ = 'mymodels'
id = Column(Integer, primary_key=True)
_date_start = Column(Date, nullable=False, default=date.today)
_date_end = Column(Date, nullable=True, default=None)
@hybrid_property
def date_start(self) -> date:
return self._date_start
@hybrid_property
def date_end(self) -> date:
return self._date_end
@hybrid_property
def date_last_active(self) -> date:
return func.coalesce(self.date_end, func.current_date())
@hybrid_method
def contains(self, date: date) -> bool:
return (date >= self.date_start and date <= self.date_last_active)
@contains.expression
def contains(cls, date: date) -> bool:
return and_(date >= cls._date_start, date <= cls.date_last_active)
Then the query ...
with Session(engine) as session, session.begin():
entry = MyModel(_date_start=date(2022, 12, 1))
session.add(entry)
with Session(engine) as session, session.begin():
print(session.query(MyModel).filter(MyModel.contains(date(2022, 12, 5))).count())
... outputs something like this:
2022-12-18 11:14:31,913 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2022-12-18 11:14:31,915 INFO sqlalchemy.engine.Engine INSERT INTO mymodels (_date_start, _date_end) VALUES (%(_date_start)s, %(_date_end)s) RETURNING mymodels.id
2022-12-18 11:14:31,915 INFO sqlalchemy.engine.Engine [generated in 0.00026s] {'_date_start': datetime.date(2022, 12, 1), '_date_end': None}
2022-12-18 11:14:31,918 INFO sqlalchemy.engine.Engine COMMIT
2022-12-18 11:14:31,943 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2022-12-18 11:14:31,950 INFO sqlalchemy.engine.Engine SELECT count(*) AS count_1
FROM (SELECT mymodels.id AS mymodels_id, mymodels._date_start AS mymodels__date_start, mymodels._date_end AS mymodels__date_end
FROM mymodels
WHERE mymodels._date_start <= %(date_start_1)s AND coalesce(mymodels._date_end, CURRENT_DATE) >= %(coalesce_1)s) AS anon_1
2022-12-18 11:14:31,950 INFO sqlalchemy.engine.Engine [generated in 0.00022s] {'date_start_1': datetime.date(2022, 12, 5), 'coalesce_1': datetime.date(2022, 12, 5)}
1
2022-12-18 11:14:31,952 INFO sqlalchemy.engine.Engine COMMIT
Upvotes: 1