SQLAlchemyORMを使用して動的フィルタリングを実装しようとしています。
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)
2回目の実行後、いくつかの問題があります。
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
の代わりにfilter_by
を使用できるのが1つだけの場合、これら2つの方法に違いはありますか?
あなたの問題は、filter_byはキーワード引数を取りますが、filterは式を取ります。したがって、filter_by ** mydictのdictを展開すると機能します。フィルタを使用すると、通常、1つの引数を渡します。これはたまたま式です。したがって、** filters dictをfilterに展開すると、理解できない一連のキーワード引数をfilterに渡します。
保存されたフィルター引数の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'を作成できること、<=または日付の比較を実行できることです。 filter_byをキーワードで使用するよりもはるかに柔軟性があります。唯一の注意点は、結合の場合、誤ってテーブルを2回結合しようとしないように少し注意する必要があり、複雑なフィルタリングの結合条件を指定する必要がある場合があることです。私はこれをかなり複雑なドメインモデルの非常に複雑なフィルタリングで使用し、魅力のように機能します。結合を追跡するために、entities_joinedのdictを実行し続けます。
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()