2015-04-09 4 views
1

У меня есть класс пользователя, в котором хранится столбец «first_name» и столбец «псевдоним», чтобы описать имя человека и его псевдоним. Я хочу, чтобы иметь возможность запросить этот класс, чтобы найти пользователей, либо их first_name или их псевдонима, поэтому я написал свой класс так:SQLAlchemy: Как я могу создать гибридное свойство для дизъюнкции столбцов?

from sqlalchemy import Column, Integer, String, create_engine, orm 
from sqlalchemy.ext.declarative import declarative_base 
from 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) 

Это почти работает. Я могу искать пользователей по их имени. Но я не могу искать пользователей по их псевдонимам.

>>> 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() 
>>> 

Это делает sense-- выражений, генерируемых name не те, которые я хочу.

>>> print session.query(User).filter_by(name="Ed") 
SELECT "user".id AS user_id, "user".first_name AS user_first_name, "user".alias AS user_alias 
FROM "user" 
WHERE "user".first_name = :first_name_1 

SQLAlchemy docs on hybrid properties, кажется, предлагает решение использовать hybrid_property.expression() изменить сгенерированный выражение. Однако я не могу понять, какое выражение имеет смысл вернуться.

Моя первая идея состояла в том, чтобы вернуть своего рода OR:

from sqlalchemy.sql import or_ 

@name.expression 
def name(cls): 
    return or_(cls.first_name, cls.alias) 

Но выражения, которые он генерирует предсказуемо неправильно:

SELECT "user".id AS user_id, "user".first_name AS user_first_name, "user".alias AS user_alias 
FROM "user" 
WHERE ("user".first_name OR "user".alias) = :param_1 

Как я могу сделать эту работу?

ответ

2

После некоторого изучения и экспериментов выяснилось, что решение описано в разделе «Building Custom Comparators». Вы можете использовать объект компаратор для реализации пользовательских сравнения:

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) 

, который работает, как ожидалось:

>>> user 
User("Edward" a.k.a. "Ed") 
>>> print session.query(User).filter_by(name="Edward") 
SELECT "user".id AS user_id, "user".first_name AS user_first_name, "user".alias AS user_alias 
FROM "user" 
WHERE "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") 

Я думаю, что есть более улучшение комнаты здесь, изучая «Hybrid Value Objects», однако, по-прежнему уклоняется от меня.