This kinda works
import dataclasses
from typing import List
from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import operators
from sqlalchemy.sql.elements import BinaryExpression, BindParameter
Base = declarative_base()
@dataclasses.dataclass
class MyObject:
attr: int
class MyObjectModel(Base):
__tablename__ = "object"
attr = Column(Integer, nullable=False, primary_key=True)
def eval_binary_expression(expression: BinaryExpression, obj, eval_as=None) -> bool:
if eval_as:
obj = eval_as(**obj.__dict__)
def eval_value(value):
if isinstance(value, BinaryExpression):
return eval_binary_expression(value, obj)
if isinstance(value, BindParameter):
return value.value
if isinstance(value, Column):
if value.table.fullname == obj.__tablename__:
return getattr(obj, value.name)
raise ValueError(f"Column {value.name} is not from table {eval_as.__tablename__}")
return value
left = eval_value(expression.left)
right = eval_value(expression.right)
if expression.operator == operators.in_op:
return left in right
return expression.operator(left, right)
def sqlalchemy_operator_to_python(operator):
if operator == operators.in_op:
return lambda left, right: left in right
elif operator == operators.not_in_op:
return lambda left, right: left not in right
elif operator == operators.is_:
return lambda left, right: left == right
elif operator == operators.isnot:
return lambda left, right: left != right
return operator
obj_list: List[MyObject] = [MyObject(23), MyObject(42), MyObject(25)]
print([obj for obj in obj_list if eval_binary_expression(MyObjectModel.attr > 24, obj, MyObjectModel)])
# [MyObject(attr=42), MyObject(attr=25)]
print([obj for obj in obj_list if eval_binary_expression(MyObjectModel.attr.in_([23, 50]), obj, MyObjectModel)])
# [MyObject(attr=23)]
First of all, BinaryExpression is a class to make an SQL Statement so you need some workaround to apply it to Python. So, I think this is something you have to compromise.
You need to check all operator you want to use in sqlalchemy_operator_to_python, but this works.