python - SQLAlchemy: How can I create a hybrid property for the disjunction of columns? -


i have user class stores "first_name" column , "alias" column describe person's name , nickname. want able query class find users either first_name or alias, wrote class this:

from sqlalchemy import column, integer, string, create_engine, orm sqlalchemy.ext.declarative import declarative_base sqlalchemy.ext.hybrid import hybrid_property   engine = create_engine('sqlite:///:memory:') base = declarative_base() session = orm.sessionmaker(engine)   class user(base):     __tablename__ = 'user'     id = column(integer, primary_key=true)     first_name = column(string(64))     alias = column(string(64), nullable=true)      @hybrid_property     def name(self):         return self.alias or self.first_name      def __repr__(self):         return 'user("{}" a.k.a. "{}")'.format(self.first_name, self.alias)   if __name__ == "__main__":     base.metadata.create_all(engine) 

this works. i'm able search users first name. i'm not able search users aliases.

>>> session = session() >>> user = user(first_name="edward", alias="ed") >>> session.add(user) >>> session.flush() >>> session.query(user).filter_by(name="edward").first() user("edward" a.k.a. "ed") >>> session.query(user).filter_by(name="ed").first() >>> 

this makes sense-- expressions generated name not ones want.

>>> print session.query(user).filter_by(name="ed") select "user".id user_id, "user".first_name user_first_name, "user".alias user_alias "user" "user".first_name = :first_name_1 

the sqlalchemy docs on hybrid properties seems suggest solution use hybrid_property.expression() change generated expression. however, can't see expression make sense return.

my first idea return sort of or:

from sqlalchemy.sql import or_  @name.expression def name(cls):     return or_(cls.first_name, cls.alias) 

but expressions generates predictably wrong:

select "user".id user_id, "user".first_name user_first_name, "user".alias user_alias "user" ("user".first_name or "user".alias) = :param_1 

how can make work?

after more studying , experimentation, turns out solution described in "building custom comparators" section. can use comparator object implement custom comparisons:

class either(comparator):     def __init__(self, left, right):         self.left = left         self.right = right      def __eq__(self, other):         return or_(other == self.left, other == self.right)   class user(base):     # ...      @name.comparator     def name(self):         return either(self.first_name, self.alias) 

which works expected:

>>> user user("edward" a.k.a. "ed") >>> print session.query(user).filter_by(name="edward") select "user".id user_id, "user".first_name user_first_name, "user".alias user_alias "user" "user".first_name = :first_name_1 or "user".alias = :alias_1 >>> print session.query(user).filter_by(name="edward").first() user("edward" a.k.a. "ed") >>> print session.query(user).filter_by(name="ed").first() user("edward" a.k.a. "ed") 

i think there more room improvement here studying "hybrid value objects", however, it's still evading me.


Popular posts from this blog