我正在寻找一种使用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函数(code here)。
我在SQLAlchemy文档中搜索并发现了两个潜在的潜在客户,但似乎都不满意:
使用Column.like
,Column.in_
...:这些运算符可直接在列上使用,这样可以使getattr
变得简单,但有些仍然缺失({{1} },==
等。)
使用>
:例如Column.op
但这似乎并不适用于所有运营商(Cat.name.op('=')('Hobbes')
)。
如果没有in
函数,有没有一种干净的方法呢?
答案 0 :(得分:43)
如果这对某人有用,这就是我最终要做的事情:
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
==
{li> lt
<
{li> ge
>=
{li> in
in_
{li> like
like
可以找到具有相应名称的详尽列表here。
答案 1 :(得分:16)
构建多个表达式过滤器时的一个有用技巧:
filter_group = list(Column.in_('a','b'),Column.like('%a'))
query = query.filter(and_(*filter_group))
使用此方法将允许您将表达式与和/或逻辑组合在一起。 此外,这将允许您避免像答案中那样的递归调用。
答案 2 :(得分:1)
您可以使用 sqlalchemy-elasticquery 使用SQLAlchemy构建动态过滤器。
?filters={ "age" : 3 }
答案 3 :(得分:0)
class Place(db.Model):
id = db.Column(db.Integer, primary_key=True)
search_id = db.Column(db.Integer, db.ForeignKey('search.id'), nullable=False)
@classmethod
def dinamic_filter(model_class, filter_condition):
'''
Return filtered queryset based on condition.
:param query: takes query
:param filter_condition: Its a list, ie: [(key,operator,value)]
operator list:
eq for ==
lt for <
ge for >=
in for in_
like for like
value could be list or a string
:return: queryset
'''
__query = db.session.query(model_class)
for raw in filter_condition:
try:
key, op, value = raw
except ValueError:
raise Exception('Invalid filter: %s' % raw)
column = getattr(model_class, key, None)
if not column:
raise Exception('Invalid filter column: %s' % key)
if op == 'in':
if isinstance(value, list):
filt = column.in_(value)
else:
filt = column.in_(value.split(','))
else:
try:
attr = list(filter(lambda e: hasattr(column, e % op), ['%s', '%s_', '__%s__']))[0] % op
except IndexError:
raise Exception('Invalid filter operator: %s' % op)
if value == 'null':
value = None
filt = getattr(column, attr)(value)
__query = __query.filter(filt)
return __query
执行方式:
places = Place.dinamic_filter([('search_id', 'eq', 1)]).all()
答案 4 :(得分:0)
改进https://stackoverflow.com/a/14876320/12562701,可以获得更复杂的过滤器
字典以映射op
dict_filtros_op = {
'==':'eq',
'!=':'ne',
'>':'gt',
'<':'lt',
'>=':'ge',
'<=':'le',
'like':'like',
'ilike':'ilike',
'in':'in'
}
Dao类:
class BaseDao():
@classmethod
@init_db_connection
def create_query_select(cls, model, filters=None, columns=None):
return cls.db_session.query(*cls.create_query_columns(model=model, columns=columns))\
.filter(*cls.create_query_filter(model=model, filters=filters))
@classmethod
def create_query_filter(cls, model, filters):
'''
return sqlalchemy filter list
Args:
model:sqlalchemy model (classe das tabelas)
filters: filter dict
ex:
filters = {
'or_1':{
'and_1':[('id', '>', 5),('id', '!=', 3)],
'and_2':[('fase', '==', 'arquivado')]
},
'and':[('test', '==', 'test')]
}
Returns:
filt: sqlalchemy filter list
'''
if not filters:
return []
filt = []
for condition in filters:
if type(filters[condition]) == dict:
if 'and' in condition:
filt.append(and_(*cls.create_query_filter(model, filters[condition])))
elif 'or' in condition:
filt.append(or_(*cls.create_query_filter(model, filters[condition])))
else:
raise Exception('Invalid filter condition: %s' % condition)
continue
filt_aux = []
for t_filter in filters[condition]:
try:
column_name, op, value = t_filter
except ValueError:
raise Exception('Invalid filter: %s' % t_filter)
if not op in dict_filtros_op:
raise Exception('Invalid filter operation: %s' % op)
column = getattr(model, column_name, None)
if not column:
raise Exception('Invalid filter column: %s' % column_name)
if dict_filtros_op[op] == 'in':
filt.append(column.in_(value))
else:
try:
attr = list(filter(lambda e: hasattr(column, e % dict_filtros_op[op]), ['%s', '%s_', '__%s__']))[0] % dict_filtros_op[op]
except IndexError:
raise Exception('Invalid filter operator: %s' % dict_filtros_op[op])
if value == 'null':
value = None
filt_aux.append(getattr(column, attr)(value))
if 'and' in condition:
filt.append(and_(*filt_aux))
elif 'or' in condition:
filt.append(or_(*filt_aux))
else:
raise Exception('Invalid filter condition: %s' % condition)
return filt
@classmethod
def create_query_columns(cls, model, columns):
'''
Return a list of attributes (columns) from the class model
Args:
model: sqlalchemy model
columns: string list
ex: ['id', 'cnj']
Returns:
cols: list of attributes from the class model
'''
if not columns:
return [model]
cols = []
for column in columns:
attr = getattr(model, column, None)
if not attr:
raise Exception('Invalid column name %s' % column)
cols.append(attr)
return cols