Подтвердить что ты не робот

SQLAlchemy DetachedInstanceError с регулярным атрибутом (а не отношением)

Я только начал использовать SQLAlchemy и получить DetachedInstanceError и не могу найти много информации об этом в любом месте. Я использую экземпляр вне сеанса, поэтому естественно, что SQLAlchemy не может загружать какие-либо отношения, если они еще не загружены, однако атрибут, к которому я обращаюсь, не является отношением, на самом деле этот объект вообще не имеет отношения. Я нашел решения, такие как нетерпеливая загрузка, но я не могу применить к этому, потому что это не отношение. Я даже пытался "прикоснуться" к этому атрибуту до закрытия сеанса, но он все равно не предотвращает исключение. Что может вызвать это исключение для нереляционного свойства даже после того, как он был успешно достигнут один раз раньше? Любая помощь в отладке этой проблемы приветствуется. В то же время я попытаюсь получить воспроизводимый автономный сценарий и обновить его здесь.

Обновление: это фактическое сообщение с несколькими стеками:

  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 159, in __get__
    return self.impl.get(instance_state(instance), instance_dict(instance))
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
    value = callable_(passive=passive)
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/state.py", line 280, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/mapper.py", line 2323, in _load_scalar_attributes
    (state_str(state)))
DetachedInstanceError: Instance <ReportingJob at 0xa41cd8c> is not bound to a Session; attribute refresh operation cannot proceed

Частичная модель выглядит следующим образом:

metadata = MetaData()
ModelBase = declarative_base(metadata=metadata)

class ReportingJob(ModelBase):
    __tablename__ = 'reporting_job'

    job_id         = Column(BigInteger, Sequence('job_id_sequence'), primary_key=True)
    client_id      = Column(BigInteger, nullable=True)

И поле client_id - это то, что вызывает это исключение, с использованием, как показано ниже:

Query:

    jobs = session \
            .query(ReportingJob) \
            .filter(ReportingJob.job_id == job_id) \
            .all()
    if jobs:
        # FIXME(Hari): Workaround for the attribute getting lazy-loaded.
        jobs[0].client_id
        return jobs[0]

Это то, что затем вызывает исключение из области сеанса:

        msg = msg + ", client_id: %s" % job.client_id
4b9b3361

Ответ 1

Я нашел основную причину, пытаясь сузить код, вызвавший исключение. Я поместил один и тот же код доступа в разные места после закрытия сеанса и обнаружил, что он определенно не вызывает никаких проблем сразу после закрытия сеанса запроса. Оказывается, проблема начинается после закрытия нового сеанса, который открывается для обновления объекта. Как только я понял, что состояние объекта непригодно после закрытия сеанса, я смог найти этот поток, который обсуждал эту же проблему. Два решения, выходящие из потока:

  • Держите сессию открытой (что очевидно)
  • Укажите expire_on_commit=False - sessionmaker().

Третий вариант - вручную установить expire_on_commit на False в сеансе после его создания, например: session.expire_on_commit = False. Я подтвердил, что это решает мою проблему.

Ответ 2

Мы получали аналогичные ошибки, даже если expire_on_commit установлен на False. В конце концов, это было вызвано тем, что два sessionmaker, которые оба привыкли делать сеансы в разных запросах. Я не понимаю, что происходит, но если вы видите это исключение с expire_on_commit=False, убедитесь, что у вас нет двух sessionmaker инициализированных.

Ответ 3

Что касается меня (новичок), я допустил ошибку в отступе и закрыл сеанс внутри моего цикла, в котором я петлю каждую строку, выполняю операцию и фиксирую каждый раз.

Итак, для тех новичков, как я, проверьте свой код перед настройкой таких вещей, как expire_on_commit=False, это может привести к вашей другой ловушке.