[英]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的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
范围界定,而只是将事务悬而未决,直到我稍后(明确)提交。
所以我的问题可以归结为:
Session
? 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
很有可能。 在正确地将数据正确保存到数据库的范围内,它的用法正确。 但是,由于您的事务仅涉及更新,因此在更新同一行时可能会遇到争用情况。 取决于应用程序,这可以。
不过期属性是正确的方法。 默认情况下到期的原因是因为它可以确保即使是天真的代码也可以正常工作。 如果您小心的话,那应该不是问题。
将事务处理的概念与会话的概念分开很重要。 contextmanager
做两件事:它维护会话以及事务。 每个ORM实例的生命周期仅限于每个事务的范围 。 这样,您可以假定对象的状态与数据库中相应行的状态相同。 这就是框架在提交时使属性过期的原因,因为它不能再保证事务提交后值的状态。 因此,您只能在事务处于活动状态时访问实例的属性。
提交后,您访问的任何后续属性都将导致启动新事务,以便ORM可以再次保证数据库中值的状态。
但是,为什么会出现错误? 这是因为您的会话已消失,所以ORM无法启动事务。 如果在上下文管理器块的中间执行session.commit()
,则访问其中一个属性时,您会注意到新的事务正在启动。
好吧,如果我只想访问以前获取的值怎么办? 然后,您可以要求框架不要使那些属性过期。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.