Python SQLAlchemy中过滤器的动态构造

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

我正在寻找一种使用SQLAlchemy动态构造过滤器的方法。也就是说,给定列、运算符名称和比较值,构造相应的过滤器

我将尝试使用一个示例来说明(这将用于构建API)。假设我们有以下模型:

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)

我环顾四周,想看看它是如何完成的,但找不到一种方法不涉及手动将每个操作符名称映射到一个比较器或类似的东西。例如,保留所有受支持操作的字典,并存储相应的lambda函数()

我在SQLAlchemy文档中搜索,发现了两条潜在的线索,但两条线索都不令人满意:

  • 使用
    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
    用于
    =
  • in
    for
    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