在SQLAlchemy中动态构建过滤器

45 投票
5 回答
24513 浏览
提问于 2025-04-17 15:41

我想找到一种方法,能够动态地使用SQLAlchemy构建过滤器。也就是说,给定一个列名、一个操作符和一个比较值,能够构建出相应的过滤器。

我来举个例子(这会用于构建一个API)。假设我们有以下模型:

class Cat(Model):

  id = Column(Integer, primary_key=True)
  name = Column(String)
  age = Column(Integer)

我想把查询映射到过滤器。例如,

  • /cats?filter=age;eq;3 应该生成 Cat.query.filter(Cat.age == 3)

  • /cats?filter=age;in;5,6,7&filter=id;ge;10 应该生成 Cat.query.filter(Cat.age.in_([5, 6, 7])).filter(Cat.id >= 10)

我查了一下,想看看别人是怎么做的,但没找到一种不需要手动将每个操作符映射到比较器的方法。比如,Flask-Restless 会维护一个所有支持操作的字典,并存储相应的lambda函数(代码在这里)。

我在SQLAlchemy的文档中找到了两个可能的线索,但都不太令人满意:

  • 使用 Column.likeColumn.in_ 等:这些操作符可以直接在列上使用,这样用 getattr 会很简单,但有些操作符还是缺失的(比如 ==> 等)。

  • 使用 Column.op:例如 Cat.name.op('=')('Hobbes'),但这似乎并不适用于所有操作符(尤其是 in)。

有没有一种干净的方法可以做到这一点,而不需要使用 lambda 函数?

5 个回答

2

你可以使用 sqlalchemy-elasticquery 来动态构建过滤器,这个工具是基于SQLAlchemy的。

?filters={ "age" : 3 }
24

在构建多个表达式过滤器时,有一个很实用的小技巧:

filter_group = list(Column.in_('a','b'),Column.like('%a'))
query = query.filter(and_(*filter_group))

使用这种方法可以让你把表达式用“与”或“或”的逻辑结合起来。这样做还可以避免像你回答中提到的那样使用递归调用。

60

如果这对某些人有帮助的话,我分享一下我最后做的事情:

from flask import request

class Parser(object):

  sep = ';'

  # ...

  def filter_query(self, query):
    model_class = self._get_model_class(query) # returns the query's Model
    raw_filters = request.args.getlist('filter')
    for raw in raw_filters:
      try:
        key, op, value = raw.split(self.sep, 3)
      except ValueError:
        raise APIError(400, 'Invalid filter: %s' % raw)
      column = getattr(model_class, key, None)
      if not column:
        raise APIError(400, 'Invalid filter column: %s' % key)
      if op == 'in':
        filt = column.in_(value.split(','))
      else:
        try:
          attr = filter(
            lambda e: hasattr(column, e % op),
            ['%s', '%s_', '__%s__']
          )[0] % op
        except IndexError:
          raise APIError(400, 'Invalid filter operator: %s' % op)
        if value == 'null':
          value = None
        filt = getattr(column, attr)(value)
      query = query.filter(filt)
    return query

这涵盖了所有SQLAlchemy的列比较器:

  • eq代表==(相等)
  • lt代表<(小于)
  • ge代表>=(大于等于)
  • in代表in_(在某个集合中)
  • like代表like(类似于)
  • 等等。

完整的列表和对应的名称可以在这里找到。

撰写回答