SQLAlchemy会话的上下文/作用域是否需要非自动对象/属性到期? [英] Does Context/Scoping of a SQLAlchemy Session Require Non-Automatic Object/Attribute Expiration?

查看:95
本文介绍了SQLAlchemy会话的上下文/作用域是否需要非自动对象/属性到期?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在处理的应用程序中,特定类的实例在其生命周期的 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:

  1. 在我描述的情况下,是否适当地使用了作用域/上下文管理的Session?
  2. 是否有另一种引用过期属性的方法是更好/更优选的方法? (例如,使用属性来包装捕获到期/分离的异常的步骤,或者创建并更新镜像" ORM链接的过期属性的非ORM链接的属性)
  3. 我是否误解或滥用了SQLAlchemy Session和ORM?在我看来,使用contextmanager方法是矛盾的,因为这种方法无法随后引用任何持久属性,即使对于像日志这样简单且广泛适用的任务也是如此.
  1. Is a scoped/context-managed Session being used appropriately in the case I described?
  2. 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)
  3. Am I misunderstanding or misusing the SQLAlchemy Session and ORM? It seems contradictory to me to use a contextmanager 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆