Python SQLAlchemy中过滤器的动态构造
我正在寻找一种使用SQLAlchemy动态构造过滤器的方法。也就是说,给定列、运算符名称和比较值,构造相应的过滤器 我将尝试使用一个示例来说明(这将用于构建API)。假设我们有以下模型:Python SQLAlchemy中过滤器的动态构造,python,sqlalchemy,Python,Sqlalchemy,我正在寻找一种使用SQLAlchemy动态构造过滤器的方法。也就是说,给定列、运算符名称和比较值,构造相应的过滤器 我将尝试使用一个示例来说明(这将用于构建API)。假设我们有以下模型: class Cat(Model): id = Column(Integer, primary_key=True) name = Column(String) age = Column(Integer) 我想将查询映射到过滤器。比如说, /cats?过滤器=年龄;情商;3应生成Cat.query
class Cat(Model):
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
我想将查询映射到过滤器。比如说,
应生成/cats?过滤器=年龄;情商;3
Cat.query.filter(Cat.age==3)
应生成/cats?过滤器=年龄;在里面5,6,7&过滤器=id;通用电气;10
Cat.query.filter(Cat.age.in.[5,6,7]).filter(Cat.id>=10)
- 使用
,Column.like
..:这些操作符可以直接在列上使用,这将使使用Column.in.
变得简单,但是仍然缺少一些操作符(getattr
,=
,等等) - 使用
:例如Column.op
,但这似乎并不适用于所有操作符(中的Cat.name.op('=')('Hobbes')
)
有没有一种干净的方法可以在没有lambda函数的情况下执行此操作?如果这对某人有用,下面是我最后要做的:
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列比较器:
=
用于lt
=
forin
in
用于like
like
- 等等
可以找到包含相应名称的详尽列表。构建多表达式过滤器时的一个有用技巧:
filter_group = list(Column.in_('a','b'),Column.like('%a'))
query = query.filter(and_(*filter_group))
使用这种方法将允许您将表达式与and/or逻辑结合起来。
此外,这将允许您避免像在回答中那样的递归调用。您可以使用使用SQLAlchemy动态构造过滤器
?filters={ "age" : 3 }
通过改进,您可以获得更复杂的过滤器
口述地图操作
dict_filtros_op = {
'==':'eq',
'!=':'ne',
'>':'gt',
'<':'lt',
'>=':'ge',
'<=':'le',
'like':'like',
'ilike':'ilike',
'in':'in'
}
这对于
或
很有用,但是链接过滤器与使用和
之间没有区别。哪里有递归调用?我的错。我把query=query.filter(filt)
误解为对filter\u query
的递归调用,不用担心-谢谢你的建议,如果你决定实现更复杂的过滤器,它可能会派上用场。先生,你是个天才。改编了这个片段,效果非常好。太好了!这正是我一直在努力解决的问题。你能解释一下lambda是如何工作的吗?它现在被包装在一个过滤器函数中,但引用过滤器函数外部的字符串扩展器。这会导致潜在的问题吗?@mtth:你能详细解释一下下面的代码片段:attr=filter(lambda e:hasattr(column,e%op),['%s','%s',''.\uuuu%s\uuu']][0]%op
部分吗?在python 3中,将行更改为attr=list(filter(lambda e:hasattr(column,e%op\u name),['%s'、'%s'、'.\uuu%s\uuuu'])[0]%op\u name
否则您会得到TypeError:'filter'对象不可订阅
places = Place.dinamic_filter([('search_id', 'eq', 1)]).all()
dict_filtros_op = {
'==':'eq',
'!=':'ne',
'>':'gt',
'<':'lt',
'>=':'ge',
'<=':'le',
'like':'like',
'ilike':'ilike',
'in':'in'
}
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