解决SQLAlchemy中使用event.listens的`DetachedInstanceError`

1 投票
1 回答
1558 浏览
提问于 2025-04-17 13:18

我遇到的问题和这个问题很相似,不过即使我尝试了类似下面的做法:

...
from my_app.models import Session
user = Session.merge(user)
new_foo = models.Foo(user=user)
...

我基本上是从请求中获取用户模型对象,然后试图创建一个与用户有关联的新Foo对象,但却出现了DetachedInstanceError错误。我觉得这是因为我使用的event.listens在后面使用了不同的Session

我的监听函数看起来是这样的:

@event.listens_for(mapper, 'init')
def auto_add(target, args, kwargs):
    Session.add(target)

Session的定义是:

Session = scoped_session(sessionmaker())

如果我依赖event.listens来将目标添加到Session中,我该如何确保像用户这样的对象在请求上下文中被正确处理呢?

让我能够解决这个问题的唯一方法是调用sessionmaker并设置expire_on_commit=False,但我觉得这不是我应该做的,因为根据很棒的SQLA文档,它:

commit()的另一个行为是,默认情况下,它会在提交完成后使所有实例的状态失效。这是为了确保当下次访问这些实例时,无论是通过属性访问还是在查询结果集中,它们都能接收到最新的状态。要禁用此行为,可以将sessionmaker配置为expire_on_commit=False

我希望能获取用户对象的最新状态。我该如何在合适的地方处理merge呢?

实际的错误追踪信息(特定于网络框架的行已删除)看起来是这样的:

  File "/Users/alfredo/python/my_app/my_app/models/users.py", line 31, in __repr__
    return '<User %r>' % self.username
  File "/Users/alfredo/.virtualenvs/my_app/lib/python2.7/site-packages/SQLAlchemy-0.8.0b2-py2.7-macosx-10.8-intel.egg/sqlalchemy/orm/attributes.py", line 251, in __get__
    return self.impl.get(instance_state(instance), dict_)
  File "/Users/alfredo/.virtualenvs/my_app/lib/python2.7/site-packages/SQLAlchemy-0.8.0b2-py2.7-macosx-10.8-intel.egg/sqlalchemy/orm/attributes.py", line 543, in get
    value = callable_(passive)
  File "/Users/alfredo/.virtualenvs/my_app/lib/python2.7/site-packages/SQLAlchemy-0.8.0b2-py2.7-macosx-10.8-intel.egg/sqlalchemy/orm/state.py", line 376, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "/Users/alfredo/.virtualenvs/my_app/lib/python2.7/site-packages/SQLAlchemy-0.8.0b2-py2.7-macosx-10.8-intel.egg/sqlalchemy/orm/loading.py", line 554, in load_scalar_attributes
    (state_str(state)))
DetachedInstanceError: Instance <User at 0x10986d1d0> is not bound to a Session; attribute refresh operation cannot proceed

这个问题发生的方法是:

def post(self, *args, **kw):
    # Actually create one
    new_foo = Foo(user=request.context['user'], title=kw['title'], body=kw['body'])
    new_foo.flush()
    redirect('/pages/')

上面的问题是,我从请求上下文中获取User对象,而这发生在一个不同的Session中(或者至少,我是这么认为的)。

编辑:似乎是__repr__的使用给我带来了麻烦,在请求的某个时刻,模型的字符串表示被调用,而我的模型的这一部分让我陷入了困境:

def __repr__(self):
    return '<User %r>' % self.username

如果我不实现这个方法,之前的一切都能按预期工作。我该怎么做才能防止在调用repr时出现这个问题呢?

1 个回答

0

我在用nosetest和sqlalchemy的时候也遇到了同样的错误。在我的情况下,调用 logging.getLogger('foo').debug('data %s', mydata) 时出现了这个错误。这里的'mydata'是一个sqlalchemy映射的实例,但还没有提交。我的解决办法是用 logging.getLogger('foo').debug('data %s', repr(mydata))

你能把 __repr__ 方法改成下面这样,来帮助你找到问题吗?

def __repr__(self):
    try:
        return '<User %r>' % self.username
    except:
        return 'I got it!'

撰写回答