繁体   English   中英

SQLAlchemy会话的上下文/作用域是否需要非自动对象/属性过期?

[英]Does Context/Scoping of a SQLAlchemy Session Require Non-Automatic Object/Attribute Expiration?

情况:具有基本属性的简单类

在我正在处理的应用程序中,特定类的实例在其生命周期结束时会保留下来,并且尽管随后没有对其进行修改,但可能需要读取其属性。 例如,实例的end_time或其相对于同一类其他实例的顺序位置(初始化的第一个实例的值为1,下一个实例的值为2, end_time )。

class Foo(object):
    def __init__(self, position):
        self.start_time = time.time()
        self.end_time = None
        self.position = position
        # ...
    def finishFoo(self):
        self.end_time = time.time()
        self.duration = self.end_time - self.start_time
    # ...

目标:使用SQLAlchemy保留实例

以下是我认为是一个最佳实践-使用范围的SQLAlchemy的Session ,因为这里建议 ,通过的方式contextlib.contextmanager -我保存例如在新创建的Session并立即提交。 下一行通过在日志记录中提及新持久实例来引用新持久实例,这将引发DetachedInstanceError因为其引用的属性在Session提交时已过期。

class Database(object):
    # ...
    def scopedSession(self):
        session = self.sessionmaker()
        try:
            yield session
            session.commit()
        except:
            session.rollback()
            logger.warn("blah blah blah...")
        finally:
            session.close()
    # ...
    def saveMyFoo(self, foo):
        with self.scopedSession() as sql_session:
            sql_session.add(foo)
        logger.info("Foo number {0} finished at {1} has been saved."
                    "".format(foo.position, foo.end_time))
        ## Here the DetachedInstanceError is raised

两种已知的可能解决方案:无过期或无范围

我知道我可以将expire_on_commit标志设置为False来解决此问题,但是我担心这是一个有问题的做法-由于存在自动到期的原因,我很犹豫将所有与ORM关联的类任意组合为一个非类-到期状态,没有足够的理由和背后的理解。 另外,我可以忘记对Session范围界定,而只是将事务悬而未决,直到我稍后(明确)提交。

所以我的问题可以归结为:

  1. 在我描述的情况下,是否适当地使用了作用域/上下文管理的Session
  2. 有没有其他更好的方法来引用过期属性? (例如,使用属性来包装捕获到期/分离的异常的步骤,或者创建和更新“镜像” ORM链接的过期属性的非ORM链接的属性)
  3. 我是误解还是滥用了SQLAlchemy Session和ORM? 在这种情况下,即使对于像日志这样简单且广泛适用的任务也无法使用随后引用任何持久属性的能力,使用contextmanager方法对我来说似乎是矛盾的。

实际异常回溯

上面的示例已简化为专注于手头的问题,但如果有用,这里是产生的实际确切回溯。 当问题出现str.format()在运行logger.debug()调用,它试图执行Set实例的__repr__()方法。

Unhandled Error
Traceback (most recent call last):
  File "/opt/zenith/env/local/lib/python2.7/site-packages/twisted/python/log.py", line 73, in callWithContext
    return context.call({ILogContext: newCtx}, func, *args, **kw)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/twisted/python/context.py", line 118, in callWithContext
    return self.currentContext().callWithContext(ctx, func, *args, **kw)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/twisted/python/context.py", line 81, in callWithContext
    return func(*args,**kw)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/twisted/internet/posixbase.py", line 614, in _doReadOrWrite
    why = selectable.doRead()
--- <exception caught here> ---
  File "/opt/zenith/env/local/lib/python2.7/site-packages/twisted/internet/udp.py", line 248, in doRead
    self.protocol.datagramReceived(data, addr)
  File "/opt/zenith/operations/network.py", line 311, in datagramReceived
    self.reactFunction(datagram, (host, port))
  File "/opt/zenith/operations/schema_sqlite.py", line 309, in writeDatapoint
    logger.debug("Data written: {0}".format(dataz))
  File "/opt/zenith/operations/model.py", line 1770, in __repr__
    repr_info = "Set: {0}, User: {1}, Reps: {2}".format(self.setNumber, self.user, self.repCount)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 239, in __get__
    return self.impl.get(instance_state(instance), dict_)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py", line 589, in get
    value = callable_(state, passive)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/sqlalchemy/orm/state.py", line 424, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "/opt/zenith/env/local/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 563, in load_scalar_attributes
    (state_str(state)))
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Set at 0x1c96b90> is not bound to a Session; attribute refresh operation cannot proceed

1。

很有可能。 在正确地将数据正确保存到数据库的范围内,它的用法正确。 但是,由于您的事务仅涉及更新,因此在更新同一行时可能会遇到争用情况。 取决于应用程序,这可以。

2。

不过期属性是正确的方法。 默认情况下到期的原因是因为它可以确保即使是天真的代码也可以正常工作。 如果您小心的话,那应该不是问题。

3。

将事务处理的概念与会话的概念分开很重要。 contextmanager做两件事:它维护会话以及事务。 每个ORM实例的生命周期仅限于每个事务的范围 这样,您可以假定对象的状态与数据库中相应行的状态相同。 这就是框架在提交时使属性过期的原因,因为它不能再保证事务提交后值的状态。 因此,您只能在事务处于活动状态时访问实例的属性。

提交后,您访问的任何后续属性都将导致启动新事务,以便ORM可以再次保证数据库中值的状态。

但是,为什么会出现错误? 这是因为您的会话已消失,所以ORM无法启动事务。 如果在上下文管理器块的中间执行session.commit() ,则访问其中一个属性时,您会注意到新的事务正在启动。

好吧,如果我只想访问以前获取的值怎么办? 然后,您可以要求框架不要使那些属性过期。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM