python - query - sqlalchemy relationship
Construyendo dinámicamente filtros en SQLAlchemy (3)
Estoy buscando una manera de construir filtros dinámicamente usando SQLAlchemy. Es decir, dada la columna, el nombre del operador y el valor de comparación, construya el filtro correspondiente.
Trataré de ilustrar usando un ejemplo (esto se usaría para construir una API). Digamos que tenemos el siguiente modelo:
class Cat(Model):
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
Me gustaría mapear las consultas a los filtros. Por ejemplo,
/cats?filter=age;eq;3
debe generarCat.query.filter(Cat.age == 3)
/cats?filter=age;in;5,6,7&filter=id;ge;10
debe generarCat.query.filter(Cat.age.in_([5, 6, 7])).filter(Cat.id >= 10)
Miré a mi alrededor para ver cómo se había hecho, pero no pude encontrar una manera que no implicara mapear manualmente el nombre de cada operador a un comparador o algo similar. Por ejemplo, Flask-Restless mantiene un diccionario de todas las operaciones compatibles y almacena las funciones lambda correspondientes ( código aquí ).
Busqué en los documentos de SQLAlchemy y encontré dos pistas potenciales, pero ninguna me pareció satisfactoria:
utilizando
Column.like
,Column.in_
...: estos operadores están disponibles directamente en la columna, lo que simplificaría el uso degetattr
pero aún faltan algunos (==
,>
, etc.).usando
Column.op
: por ejemplo,Cat.name.op(''='')(''Hobbes'')
pero esto no parece funcionar para todos los operadores (in
concreto).
¿Hay una manera limpia de hacer esto sin funciones lambda
?
En caso de que esto sea útil para alguien, esto es lo que terminé haciendo:
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
Esto cubre todos los comparadores de columnas de SQLAlchemy:
-
eq
para==
-
lt
para<
-
ge
para>=
-
in
parain
-
like
por melike
- etc.
La lista exhaustiva con sus nombres correspondientes se puede encontrar here .
Puede usar sqlalchemy-elasticquery para construir filtros dinámicamente usando SQLAlchemy.
?filters={ "age" : 3 }
Un truco útil al construir un filtro de expresión múltiple:
filter_group = list(Column.in_(''a'',''b''),Column.like(''%a''))
query = query.filter(and_(*filter_group))
El uso de este enfoque le permitirá combinar expresiones con y / o lógica. También esto te permitirá evitar llamadas recursivas como en tu respuesta.