【问题标题】:sqlalchemy dynamic filteringsqlalchemy 动态过滤
【发布时间】:2017-05-09 09:02:46
【问题描述】:

我正在尝试使用 SQLAlchemy ORM 实现动态过滤。

我正在查看 StackOverflow 并发现非常相似的问题:SQLALchemy dynamic filter_by

对我有用,但还不够。

所以,这里有一些代码示例,我正在尝试编写:

# engine - MySQL engine
session_maker = sessionmaker(bind=engine)
session = session_maker()

# my custom model
model = User

def get_query(session, filters):
    if type(filters) == tuple:
        query = session.query(model).filter(*filters)
    elif type(filters) == dict:
        query = session.query(model).filter(**filters)
    return query

然后我试图用非常相似的东西重用它:

filters = (User.name == 'Johny')
get_query(s, filters) # it works just fine

filters = {'name': 'Johny'}
get_query(s, filters)

第二次运行后,出现了一些问题:

TypeError: filter() got an unexpected keyword argument 'name'

当我尝试将我的 filters 更改为:

filters = {User.name: 'Johny'}

它返回:

TypeError: filter() keywords must be strings

但它适用于手动查询:

s.query(User).filter(User.name == 'Johny')

我的过滤器有什么问题?

顺便说一句,看起来它适用于案例:

filters = {'name':'Johny'}
s.query(User).filter_by(**filters)

但根据上述帖子的建议,我尝试仅使用 filter

如果只能使用filter_by而不是filter,这两种方法有什么区别吗?

【问题讨论】:

    标签: python dynamic sqlalchemy filtering


    【解决方案1】:

    您的问题是 filter_by 接受关键字参数,但 filter 接受表达式。因此,扩展 filter_by **mydict 的字典将起作用。使用过滤器,您通常会传递一个参数,它恰好是一个表达式。因此,当您将 **filters 字典扩展为过滤器时,您会传递过滤器一堆它不理解的关键字参数。

    如果您想从存储的过滤器参数的字典中构建一组过滤器,您可以使用查询的生成特性来继续应用过滤器。例如:

    # assuming a model class, User, with attributes, name_last, name_first
    my_filters = {'name_last':'Duncan', 'name_first':'Iain'}
    query = session.query(User)
    for attr,value in my_filters.iteritems():
        query = query.filter( getattr(User,attr)==value )
    # now we can run the query
    results = query.all()
    

    上述模式的好处是你可以在多个连接列中使用它,你可以用and_和or_构造'ands'和'ors',你可以做

    【讨论】:

    • 这是我问题的答案。我不确定我是否会使用它来代替 filter_by,但是对于我的情况如何使用 filter 很有趣。谢谢!
    • 感谢您的精彩回答。您是否有任何示例或代码可以链接到您将其用于复杂过滤的地方?特别是,我正在尝试根据输入查询语法动态生成 and / or 操作的查询。例如"姓氏:邓肯或名字:伊恩"
    • @JacobPavlock,你可以使用sqlalchemy-filters,或者他们复制他们的api设计。
    【解决方案2】:

    我有类似的问题,尝试从字典中过滤:

    filters = {"field": "value"}
    

    错误:

    ...query(MyModel).filter(**filters).all()
    

    好:

    ...query(MyModel).filter_by(**filters).all()
    

    【讨论】:

    • 谢谢 - 现在看起来很明显,但花了我一点时间
    【解决方案3】:

    FWIW,有一个 Python 库旨在解决这个确切的问题:sqlalchemy-filters

    它允许使用所有运算符动态过滤,而不仅仅是==

    from sqlalchemy_filters import apply_filters
    
    
    # `query` should be a SQLAlchemy query object
    
    filter_spec = [{'field': 'name', 'op': '==', 'value': 'name_1'}]
    filtered_query = apply_filters(query, filter_spec)
    
    more_filters = [{'field': 'foo_id', 'op': 'is_not_null'}]
    filtered_query = apply_filters(filtered_query, more_filters)
    
    result = filtered_query.all()
    

    【讨论】:

      【解决方案4】:
      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 dynamic_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.dynamic_filter([('search_id', 'eq', 1)]).all()
      

      【讨论】:

      • 信用到期的信用:似乎这是基于stackoverflow.com/a/14876320
      • @Arjan 不是真的。但这种方式似乎解决了同样的问题。感谢您在此处添加链接。
      • 嗯,信用到期:那你在哪里找到这个?是的,我知道你在一年前发布了这个。 ;-)
      【解决方案5】:

      对于使用 FastAPI 和 SQLAlchemy 的人来说,这里是一个动态过滤的例子:

      api/app/app/crud/order.py

      from typing import Optional
      
      from pydantic import UUID4
      from sqlalchemy.orm import Session
      
      from app.crud.base import CRUDBase
      from app.models.order import Order
      from app.schemas.order import OrderCreate, OrderUpdate
      
      
      class CRUDOrder(CRUDBase[Order, OrderCreate, OrderUpdate]):
      
          def get_orders(
              self,
              db: Session,
              owner_id: UUID4,
              status: str,
              trading_type: str,
              pair: str,
              skip: int = 0,
              limit: int = 100,
          ) -> Optional[Order]:
              filters = {
                  arg: value
                  for arg, value in locals().items()
                  if arg != "self" and arg != "db" and arg != "skip" and arg != "limit" and value is not None
              }
              query = db.query(self.model)
              for attr, value in filters.items():
                  query = query.filter(getattr(self.model, attr) == value)
              return (
                  query
                  .offset(skip)
                  .limit(limit)
                  .all()
              )
      
      
      order = CRUDOrder(Order)
      
      

      【讨论】:

        猜你喜欢
        • 2016-09-17
        • 2019-03-10
        • 1970-01-01
        • 1970-01-01
        • 2022-07-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多