2013-02-10 3 views
3

Вопрос

Может ли доступ к ресурсу запускать сеанс в SQLAlchemy? Мое ожидание было бы, например, для запросов, связанных с объектом через column_property() или @hybrid_property, чтобы вызвать сеанс autoflush, так же, как запросы, сделанные с помощью session.Query(). Кажется, это не так.может ли столбец ORM запускать сеанс в SQLAlchemy?

В простом примере ниже учетная запись содержит коллекцию записей. Он также предоставляет свойство «balance», построенное с помощью функции column_property(), которая предоставляет запрос на выборку. Новые записи отображаются только в балансе учетной записи, если session.flush() вызывается явно.

Это поведение кажется субоптимальным: пользователям класса Account необходимо посыпать вызовы flush() во всем их коде, основываясь на знании внутренних компонентов реализации баланса. Если изменение реализации - например, если раньше был «баланс», могут быть введены ошибки Python @property, хотя интерфейс учетной записи по существу идентичен. Есть ли альтернатива?

Полного пример

import sys 
import sqlalchemy as sa 
import sqlalchemy.sql 
import sqlalchemy.orm 
import sqlalchemy.ext.declarative 

Base = sa.ext.declarative.declarative_base() 

class Entry(Base): 
    __tablename__ = "entries" 

    id = sa.Column(sa.Integer, primary_key=True) 
    value = sa.Column(sa.Numeric, primary_key=True) 
    account_id = sa.Column(sa.Integer, sa.ForeignKey("accounts.id")) 
    account = sa.orm.relationship("Account", backref="entries") 

class Account(Base): 
    __tablename__ = "accounts" 

    id = sa.Column(sa.Integer, primary_key=True) 
    balance = sa.orm.column_property(
     sa.sql.select([sa.sql.func.sum(Entry.value)]) 
      .where(Entry.account_id == id) 
     ) 

def example(database_url): 
    # connect to the database and prepare the schema 
    engine = sa.create_engine(database_url) 
    session = sa.orm.sessionmaker(bind=engine)() 

    Base.metadata.create_all(bind = engine) 

    # add an entry to an account 
    account = Account() 

    account.entries.append(Entry(value = 42)) 

    session.add(account) 

    # and look for that entry in the balance 
    print "account.balance:", account.balance 

    assert account.balance == 42 

if __name__ == "__main__": 
    example(sys.argv[1]) 

Наблюдаемый Выход

$ python sa_column_property_example.py postgres:///za_test 
account.balance: None 
Traceback (most recent call last): 
    File "sa_column_property_example.py", line 46, in <module> 
    example(sys.argv[1]) 
    File "sa_column_property_example.py", line 43, in example 
    assert account.balance == 42 
AssertionError 

Preferred Выход

Я хотел бы видеть "account.balance: 42", без добавления явного вызова сессии. промывать().

ответ

4

Функция column_property оценивается только во время запроса, то есть когда вы говорите запрос (Учетная запись), а также когда срок действия атрибута истек, то есть, если вы сказали session.expire («account», ['balance']).

Чтобы иметь атрибут вызывать запрос каждый раз, когда мы используем @property (несколько маленьких модников здесь для скрипта для работы с SQLite):

import sys 
import sqlalchemy as sa 
import sqlalchemy.sql 
import sqlalchemy.orm 
import sqlalchemy.ext.declarative 

Base = sa.ext.declarative.declarative_base() 

class Entry(Base): 
    __tablename__ = "entries" 

    id = sa.Column(sa.Integer, primary_key=True) 
    value = sa.Column(sa.Numeric) 
    account_id = sa.Column(sa.Integer, sa.ForeignKey("accounts.id")) 
    account = sa.orm.relationship("Account", backref="entries") 

class Account(Base): 
    __tablename__ = "accounts" 

    id = sa.Column(sa.Integer, primary_key=True) 

    @property 
    def balance(self): 
     return sqlalchemy.orm.object_session(self).query(
        sa.sql.func.sum(Entry.value) 
       ).filter(Entry.account_id == self.id).scalar() 

def example(database_url): 
    # connect to the database and prepare the schema 
    engine = sa.create_engine(database_url, echo=True) 
    session = sa.orm.sessionmaker(bind=engine)() 

    Base.metadata.create_all(bind = engine) 

    # add an entry to an account 
    account = Account() 

    account.entries.append(Entry(value = 42)) 

    session.add(account) 

    # and look for that entry in the balance 
    print "account.balance:", account.balance 

    assert account.balance == 42 

if __name__ == "__main__": 
    example("sqlite://") 

Обратите внимание, что «промывка» сам по себе, как правило, не то, мы должны волноваться; функция autoflush гарантирует, что флеш вызывается каждый раз, когда запрос() поступает в базу данных для получения результатов, поэтому он действительно гарантирует, что запрос будет таким, для чего мы идем.

Другим подходом к этой проблеме является использование гибридов. Я бы рекомендовал прочитать обзор всех трех методов в SQL Expressions as Mapped Attributes, в котором перечислены компромиссы для каждого подхода.

+0

Цените ответ. Я думаю, что я просто наклоняюсь в конструкторских решениях SQLAlchemy здесь: в ретроспективе поведение, на которое я надеялся, было чем-то вроде: «column_property должен выполнять запрос, если и только если сеанс грязный, и используйте кешированное значение в противном случае» (а также хорошо играет с выражениями запроса). Похоже, мне придется строить это поведение с помощью hybrid_property, если я действительно этого хочу. Благодарю. – bcs

+0

Ну, вы могли бы получить это, прослушивая события изменения атрибута и истекая в качестве столбца column_property. Я думаю, что очевидно, что если все свойства столбца истекли каждый раз, когда какой-либо атрибут где-нибудь был грязным, это было бы чрезмерным. column_prop не может самостоятельно решить, какие атрибуты в памяти он может зависеть (если они есть). – zzzeek

Смежные вопросы