在SQLAlchemy中动态构建过滤器
我想找到一种方法,能够动态地使用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.like
、Column.in_
等:这些操作符可以直接在列上使用,这样用getattr
会很简单,但有些操作符还是缺失的(比如==
、>
等)。使用
Column.op
:例如Cat.name.op('=')('Hobbes')
,但这似乎并不适用于所有操作符(尤其是in
)。
有没有一种干净的方法可以做到这一点,而不需要使用 lambda
函数?
5 个回答
你可以使用 sqlalchemy-elasticquery 来动态构建过滤器,这个工具是基于SQLAlchemy的。
?filters={ "age" : 3 }
在构建多个表达式过滤器时,有一个很实用的小技巧:
filter_group = list(Column.in_('a','b'),Column.like('%a'))
query = query.filter(and_(*filter_group))
使用这种方法可以让你把表达式用“与”或“或”的逻辑结合起来。这样做还可以避免像你回答中提到的那样使用递归调用。
如果这对某些人有帮助的话,我分享一下我最后做的事情:
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
(类似于)- 等等。
完整的列表和对应的名称可以在这里找到。