SQLAlchemy hybrid_property /方法可查询基于列值动态构建的datetime对象

时间:2019-04-15 15:41:28

标签: python postgresql sqlalchemy

我正在尝试在应用程序中管理api密钥。

假设此模型:

class APIKey(db.Model):
    __tablename__ = 'api_keys'
    ...
    daily_reset_time = db.Column(db.Integer)  # daily reset hour maybe use Interval or Time type here?
    daily_limit = db.Column(db.Integer)
    per_second_limit = db.Column(db.Integer)
    _calls_made = db.Column(db.Integer, default=0)
    last_started = db.Column(db.DateTime)
    last_ended = db.Column(db.DateTime)
    ...

    @property
    def last_used(self):
        return self.last_ended if self.last_ended is not None else self.last_started

一些API的每日限制会在特定时间重置。目前,我将此时间存储为代表每日重置时间的整数(也许将其存储为间隔或时间类型会更合适)。我希望能够针对calls_made,甚至更好的calls_remaining字段进行查询。为此,我将必须计算上一次重置时间,并查看last_used属性是否在daily_reset_time之前发生,以确定是否发生了重置,并因此通过设置{{ 1}}到0。instance属性通过在_calls_made对象上使用.replace方法来实现。

datetime

这显然在混合属性表达式中不起作用,因为datetime replace方法中使用的值将是@hybrid_property def previous_reset(self): if self.daily_reset_time is not None: now = datetime.utcnow() if now.hour >= self.daily_reset_time: return now.replace( hour=self.daily_reset_time, minute=0, second=0, microsecond=0 ) else: return now.replace( hour=self.daily_reset_time, minute=0, second=0, microsecond=0 ) - timedelta(days=1) 实例:

Column

查看postgres的datetime函数后,我可能需要更多类似以下内容来代替@previous_reset.expression def previous_reset(cls): now = datetime.utcnow() return case( [ ( # its later than the daily reset time and_(cls.daily_reset_time.isnot(None), cls.daily_reset_time <= now.hour), now.replace( hour=cls.daily_reset_time, # this is a valueerror minute=0, second=0, microsecond=0 ) ), ( and_(cls.daily_reset_time.isnot(None), cls.daily_reset_time >= now.hour), (now - timedelta(days=1)).replace( hour=cls.daily_reset_time, minute=0, second=0, microsecond=0 ) ), ], else_=None ) 调用作为case表达式的返回值:

now.replace

但这还缺少一些东西...

如何在混合属性表达式中计算此值?是否有更好的方法来解决此问题,因为我可能还必须添加一个信号,以检查是否应重置func.date_trunc('days', func.timezone('utc', func.current_timestamp)) + f"interval '{cls.daily_reset_time}' hours'" 属性?

1 个答案:

答案 0 :(得分:1)

您可以使用间隔支持乘法的事实:

from sqlalchemy import Interval, literal

...

(func.date_trunc('days', func.timezone('utc', func.current_timestamp())) +
 literal('1 hour').cast(Interval) * cls.daily_reset_time)

您也可以用算术替换CASE表达式。这个想法是从当前时间中减去重置时间,如果尚未达到重置时间,则将其移至前一天,将其截断为日期精度,然后将时间加回来:

@previous_reset.expression
def previous_reset(cls):
    utc_now = func.timezone('utc', func.current_timestamp())
    hour_delta = literal('1 hour').cast(Interval) * cls.daily_reset_time
    # The CASE expression is not needed even for the NULL handling, as the
    # original would produce NULL in case `daily_reset_time` was NULL, as
    # does the below expression.
    return func.date_trunc('day', utc_now - hour_delta) + hour_delta