使用SQLAlchemy创建金字塔会话时出现DetachedInstanceError

时间:2013-03-12 01:03:49

标签: python sqlalchemy pyramid zope

我编写了自己的Pyramid ISession interface实现,它应该将Session存储在数据库中。一切都很好,但不知怎的pyramid_tm引发了这个问题。一旦激活就会说:

DetachedInstanceError: Instance <Session at 0x38036d0> is not bound to a Session;
attribute refresh operation cannot proceed

(这里不要混淆:<Session ...>是模型的类名,“...到会话”最有可能是指SQLAlchemy的Session(我称之为DBSession到避免混淆)。

我查看了邮件列表和SO,似乎任何人遇到问题,他们都是

  • 产生一个新线程或
  • 手动调用transaction.commit()

我不做这些事情。然而,这里的专长是,我的会话被金字塔传递了很多。首先,我DBSession.add(session)然后return session。之后我可以使用会话,刷新消息等。

但是,一旦请求完成,我就会得到这个例外。这是完整的追溯:

Traceback (most recent call last):
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/waitress-0.8.1-py2.7.egg/waitress/channel.py", line 329, in service
    task.service()
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/waitress-0.8.1-py2.7.egg/waitress/task.py", line 173, in service
    self.execute()
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/waitress-0.8.1-py2.7.egg/waitress/task.py", line 380, in execute
    app_iter = self.channel.server.application(env, start_response)
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/pyramid/router.py", line 251, in __call__
    response = self.invoke_subrequest(request, use_tweens=True)
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/pyramid/router.py", line 231, in invoke_subrequest
    request._process_response_callbacks(response)
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/lib/python2.7/site-packages/pyramid/request.py", line 243, in _process_response_callbacks
    callback(self, response)
  File "/home/javex/data/Arbeit/libraries/python/web_projects/pyramid/miniblog/miniblog/models.py", line 218, in _set_cookie
    print("Setting cookie %s with value %s for session with id %s" % (self._cookie_name, self._cookie, self.id))
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/attributes.py", line 168, in __get__
    return self.impl.get(instance_state(instance),dict_)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/attributes.py", line 451, in get
    value = callable_(passive)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/state.py", line 285, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/mapper.py", line 1668, in _load_scalar_attributes
    (state_str(state)))
DetachedInstanceError: Instance <Session at 0x7f4a1c04e710> is not bound to a Session; attribute refresh operation cannot proceed

对于这种情况,我停用了调试工具栏。一旦我激活它就会从那里抛出错误。看来这里的问题是随时访问该对象。

我意识到我可以尝试以某种方式分离它,但这似乎不正确,因为如果没有明确地将它添加到会话中,则无法修改元素。

因此,当我没有生成新线程并且我没有显式调用commit时,我想事务是在请求完全消失之前提交,之后再次访问它。我该如何处理这个问题?

2 个答案:

答案 0 :(得分:4)

我相信你在这里看到的是一个奇怪的事实,响应回调和完成的回调实际上是在补间之后执行的。它们位于应用程序的出口和中间件之间。作为补间的pyramid_tm在响应回调执行之前提交事务 - 在以后访问时导致错误。

让这些事情的顺序正确是很困难的。我最喜欢的方法是在 pyramid_tm下注册你自己的补间,在会话中执行刷新,获取id,并在响应上设置cookie。

我同情这个问题,因为事务发生后发生的任何事情都是金字塔中的一个真正的灰色区域,并不总是很清楚会话不应该被触及。我会做一个注释,继续思考如何在未来改进Pyramid的这个工作流程。

答案 1 :(得分:2)

我首先尝试注册一个补间,它以某种方式工作,但数据没有得到保存。然后我对SQLAlchemy Event System进行了讨论。我找到了after_commit事件。使用这个,我可以在pyramid_tm完成提交后设置会话对象的分离。我认为这提供了完整的灵活性,并没有对订单施加任何要求。

我的最终解决方案:

from sqlalchemy.event import listen
from sqlalchemy.orm import Session as SASession
def detach(db_session):
    from pyramid.threadlocal import get_current_request
    request = get_current_request()
    log.debug("Expunging (detaching) session for DBSession")
    db_session.expunge(request.session)
listen(SASession, 'after_commit', detach)

唯一的缺点:它需要打电话get_current_request(),这是不鼓励的。但是,我发现无法以任何方式传递会话,因为SQLAlchemy会调用该事件。我想到了一些丑陋的包装材料,但我认为那会冒风险和不稳定。