繁体   English   中英

在 SQLAlchemy 中过滤关系属性

[英]Filtering a relationship attribute in SQLAlchemy

我有一些带有Widget对象的代码,必须定期进行一些处理。 小部件与Process对象有关系,该对象跟踪单个处理尝试并保存有关这些尝试的数据,例如状态信息、开始和结束时间以及结果。 这种关系看起来像这样:

class Widget(Base):
   _tablename_ = 'widget'
   id = Column(Integer, primary_key=True)
   name = Column(String)
   attempts = relationship('Process')

class Process(Base):
   _tablename_ = 'process'
   id = Column(Integer, primary_key=True)
   widget_id = Column(Integer, ForeignKey='widget.id'))
   start = Column(DateTime)
   end = Column(DateTime)
   success = Column(Boolean)

我想在Widget上有一个方法来检查是否是时候处理该小部件了。 它需要查看所有尝试,找到最近成功的一次,并查看它是否比阈值更旧。

一种选择是使用列表Widget.attempts迭代Widget.attempts 假设nowdelay是合理的datetimetimedelta对象,那么当定义为Widget上的方法时,类似的东西会起作用:

def ready(self):
   recent_success = [attempt for attempt in self.attempts if attempt.success is True and attempt.end >= now - delay]
   if recent_success:
      return False
   return True

这似乎是很好的惯用 Python,但它没有充分利用支持数据的 SQL 数据库的强大功能,而且它的效率可能低于运行类似的 SQL 查询,尤其是在attempts列表中有大量 Process 对象时。 不过,我很难找出将其实现为查询的最佳方法。

Widget运行查询很容易,如下所示:

def ready(self):
   recent_success = session.query(Process).filter(
      and_(
         Process.widget_id == self.id,
         Process.success == True,
         Process.end >= now - delay
      )
   ).order_by(Process.end.desc()).first()
   if recent_success:
      return False
   return True

但是我在单元测试中遇到了在定义 Widget 的模块中正确设置session问题。 在我看来,这是一个糟糕的风格选择,而且可能不是 SQLAlchemy 对象的结构方式。

我可以使ready()函数成为 Widget 类的外部函数,这将解决在单元测试中设置session的问题,但这似乎是糟糕的 OO 结构。

我认为理想的情况是,如果我能以某种方式使用比列表理解更有效的类似 SQL 的代码过滤Widget.attempts ,但我没有发现任何表明这是可能的。

对于这样的事情,实际上最好的方法是什么?

您正在朝着正确的方向思考。 Widget实例中的任何解决方案意味着您需要处理所有实例。 寻求外部过程将具有更好的性能和更容易的可测试性。

您可以使用此查询获取所有需要安排用于下一次处理的Widget实例:

q = (
    session
    .query(Widget)
    .filter(Widget.attempts.any(and_(
        Process.success == True,
        Process.end >= now - delay,
    )))
)

widgets_to_process = q.all()

如果你真的想在模型上有一个属性,我不会创建一个单独的查询,而只是使用关系:

def ready(self, at_time):
    successes = [
        attempt 
        for attempt in sorted(self.attempts, key=lambda v: v.end)
        if attempt.success and attempt.end >= at_time  # at_time = now - delay
    ]
    return bool(successes)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM