SQLAlchemy会话的上下文/作用域是否需要非自动对象/属性到期? [英] Does Context/Scoping of a SQLAlchemy Session Require Non-Automatic Object/Attribute Expiration?
问题描述
在我正在处理的应用程序中,特定类的实例在其生命周期的 end 处保留,并且随后对其进行了 的修改,但它们的属性可能需要阅读.例如,该实例的end_time
或其相对于同一类其他实例的顺序位置(初始化的第一个实例的值为1,下一个实例的值为2,依此类推.)
In an application I'm working on, instances of particular class are persisted at the end of their lifecycle, and while they are not subsequently modified, their attributes may need to be read. For example, the end_time
of the instance or its ordinal position relative to other instances of the same class (first instance initialized gets value 1, the next has value 2, etc.).
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
,如新的持久性实例在日志记录中提及该实例,该实例引发DetachedInstanceError
,因为当Session
提交时,其引用的属性已过期.
The Goal: Persist an Instance using SQLAlchemy
Following what I believe to be a best practice - using a scoped SQLAlchemy Session
, as suggested here, by way of contextlib.contextmanager
- I save the instance in a newly-created Session
which immediately commits. The very next line references the newly persistent instance by mentioning it in a log record, which throws a DetachedInstanceError
because the attribute its referencing expired when the Session
committed.
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
进行范围界定,而只是将事务挂起,直到我稍后(很长时间)显式提交为止.
Two Known Possible Solutions: No Expiring or No Scope
I know I can set the expire_on_commit
flag to False to circumvent this issue, but I'm concerned this is a questionable practice -- automatic expiration exists for a reason, and I'm hesitant to arbitrarily lump all ORM-tied classes into a non-expiry state without sufficient reason and understanding behind it. Alternatively, I can forget about scoping the Session
and just leave the transaction pending until I explicitly commit at a (much) later time.
所以我的问题可以归结为:
So my question boils down to this:
- 在我描述的情况下,是否适当地使用了作用域/上下文管理的
Session
? - 是否有另一种引用过期属性的方法是更好/更优选的方法? (例如,使用属性来包装捕获到期/分离的异常的步骤,或者创建并更新镜像" ORM链接的过期属性的非ORM链接的属性)
- 我是否误解或滥用了SQLAlchemy
Session
和ORM?在我看来,使用contextmanager
方法是矛盾的,因为这种方法无法随后引用任何持久属性,即使对于像日志这样简单且广泛适用的任务也是如此.
- Is a scoped/context-managed
Session
being used appropriately in the case I described? - Is there an alternative way to reference expired attributes that is a better/more preferred approach? (e.g. using a property to wrap the steps of catching expiration/detached exceptions or to create & update a non-ORM-linked attribute that "mirrors" the ORM-linked expired attribute)
- Am I misunderstanding or misusing the SQLAlchemy
Session
and ORM? It seems contradictory to me to use acontextmanager
approach when that precludes the ability to subsequently reference any of the persisted attributes, even for a task as simple and broadly applicable as logging.
实际异常回溯
上面的示例已简化为专注于手头的问题,但如果有用,这里是产生的实际确切回溯.当在logger.debug()
调用中运行str.format()
时,就会出现此问题,该调用试图执行Set
实例的__repr__()
方法.
The Actual Exception Traceback
The example above is simplified to focus on the question at hand, but should it be useful, here's the actual exact traceback produced. The issue arises when str.format()
is run in the logger.debug()
call, which tries to execute the Set
instance's __repr__()
method.
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.
很有可能,是的.只要正确地将数据保存到数据库中,它就可以正确使用.但是,由于您的事务仅涉及更新,因此在更新同一行时可能会遇到争用情况.取决于应用程序,这可以.
1.
Most likely, yes. It's used correctly insofar as correctly saving data to the database. However, because your transaction only spans the update, you may run into race conditions when updating the same row. Depending on the application, this can be okay.
不过期属性是正确的方法.默认情况下到期的原因是因为它可以确保即使是天真的代码也可以正常工作.如果您小心的话,那应该没问题.
Not expiring attributes is the right way to do it. The reason the expiration is there by default is because it ensures that even naive code works correctly. If you are careful, it shouldn't be a problem.
将事务处理的概念与会话的概念分开是很重要的. contextmanager
做两件事:它维护会话以及事务.每个ORM实例的生命周期仅限于每个 transaction 的范围.这样,您可以假定对象的状态与数据库中相应行的状态相同.这就是框架在提交时使属性过期的原因,因为它不能再保证在事务提交后值的状态.因此,您只能在事务处于活动状态时访问实例的属性.
It's important to separate the concept of the transaction from the concept of the session. The contextmanager
does two things: it maintains the session as well as the transaction. The lifecycle of each ORM instance is limited to the span of each transaction. This is so you can assume the state of the object is the same as the state of the corresponding row in the database. This is why the framework expires attributes when you commit, because it can no longer guarantee the state of the values after the transaction commits. Hence, you can only access the instance's attributes while a transaction is active.
提交后,您访问的任何后续属性都将导致启动新事务,以便ORM可以再次保证数据库中值的状态.
After you commit, any subsequent attribute you access will result in a new transaction being started so that the ORM can once again guarantee the state of the values in the database.
但是为什么会出现错误?这是因为您的会话已消失,所以ORM无法启动事务.如果在上下文管理器块的中间执行session.commit()
,则访问其中一个属性时,您会注意到新事务正在启动.
But why do you get an error? This is because your session is gone, so the ORM has no way of starting a transaction. If you do a session.commit()
in the middle of your context manager block, you'll notice a new transaction being started if you access one of the attributes.
好吧,如果我只想访问以前获取的值怎么办?然后,您可以要求框架不要使这些属性失效.
Well, what if I want to just access the previously-fetched values? Then, you can ask the framework not to expire those attributes.
这篇关于SQLAlchemy会话的上下文/作用域是否需要非自动对象/属性到期?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!