我正在尝试使用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
,那么这两种方法之间是否存在差异?
答案 0 :(得分:10)
你的问题是filter_by接受关键字参数,但过滤器接受表达式。因此,扩展filter_by ** mydict的dict将起作用。使用过滤器,您通常会传递一个参数,这恰好是一个表达式。因此,当您将**过滤器dict扩展为过滤器时,您会过滤掉一些它不理解的关键字参数。
如果要根据存储的过滤器参数的dict构建一组过滤器,可以使用查询的生成性质来继续应用过滤器。例如:
# 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',你可以做< =或date比较,等等。它比使用filter_by和关键字更灵活。唯一需要注意的是,对于连接,您必须要小心,不要意外地尝试连接两次表,并且可能必须为复杂过滤指定连接条件。我在一个相当复杂的域模型中使用它进行了一些非常复杂的过滤,它就像一个魅力,我只是保持一个字典来实现entity_joined以跟踪连接。
答案 1 :(得分:2)
我有一个类似的问题,试图从字典中过滤掉:
%env DATA_DIR=./data/squad
^
SyntaxError: invalid syntax
错误:
filters = {"field": "value"}
好:
...query(MyModel).filter(**filters).all()
答案 2 :(得分:2)
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()
答案 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)
对于使用FastAPI和SQLAlchemy的人,以下是动态过滤的示例:
api / app / 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)