tutorial query example español create_engine python sqlalchemy

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 generar Cat.query.filter(Cat.age == 3)

  • /cats?filter=age;in;5,6,7&filter=id;ge;10 debe generar Cat.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 de getattr 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 para in
  • like por me like
  • 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.