Sqlalchemy没有在对象刷新时调用模型的重构器

时间:2017-03-15 08:54:09

标签: python sqlalchemy flask-sqlalchemy

我有一个模型,它应该在初始化时转换一些字段。我曾尝试使用sqlalchemy.orm.reconstructor,但遇到了模型自动刷新的问题(或误解)。

这是我模特的例子:

from app import db
from sqlalchemy.orm import reconstructor

t_model = db.Table(
    'models', 
    db.metadata,
    dbc.Column('id', db.Integer, primary_key=True),
    dbc.Column('value', db.String)
)


class Model(db.Model):

  __table__ = t_order_data

  def __init__(self, value)
      self.value = value

  @reconstructor
  def init_on_load(self)
      """cast to None if value is empty string"""
      self.value = None if not self.value else self.value

  def run_db_function(self)
      try:
          result = db.session.execute(db.func.somefunc('some_param')).fetchone()
      except Exception:
          db.session.rollback()
          raise
      else:
          db.session.commit()
          return result

我的代码做了什么:

model = Model.query.get(1)  # in table this row is id=1, value=''

开始隐式事务并选择模型数据。

model.value  # returns None -- OK

然后我执行功能

model.run_db_function()

运行DB的例程,然后提交事务。

然后,当我尝试访问value属性时,sqlalchemy开始新的隐式事务,再次重新获取此模型。

但这个时间价值不会被转换

model.value  # returns '' -- NOT OK

sqlalchqmy ORM的官方文档说明了reconstructor装饰者:

  

将方法指定为“重构器”,类似于__init__的方法,在从数据库加载实例或以其他方式重构后,ORM将调用该方法。

根据这个描述,我认为在重新获取后也应该调用init_on_load,但它已经发生了。

这是错误还是设计行为?如果这是正常行为,我该怎么办?

1 个答案:

答案 0 :(得分:2)

引用官方文件:

  

reconstructor()是更大的“实例级”事件系统的快捷方式,可以使用事件API订阅 - 有关这些事件的完整API说明,请参阅InstanceEvents

实质上reconstructor()InstanceEvents.load的快捷方式:

  

通过__new__创建对象实例后,在初始属性填充发生后接收它。   ...   这通常在基于传入结果行创建实例时发生,而仅针对该实例的生命周期调用一次

当您commit或回滚时,默认值只是使会话中找到的实例的所有ORM控制属性失效。实例本身仍然活着。当您重新获取与会话中找到的实例匹配的行时,它为refreshed。一个例子:

In [2]: from sqlalchemy.events import event

In [3]: class Foo(Base):
   ...:     id = Column(Integer, primary_key=True, autoincrement=True)
   ...:     value = Column(Unicode)
   ...:     __tablename__ = 'foo'
   ...:     

添加事件监听器,以便我们可以跟踪正在进行的操作

In [5]: @event.listens_for(Foo, 'load')
   ...: def foo_load(target, context):
   ...:     print('Foo load')
   ...:     

In [6]: @event.listens_for(Foo, 'refresh')
   ...: def foo_load(target, context, attrs):
   ...:     print('Foo refresh')
   ...:     

In [7]: session.add(Foo(value='test'))

In [8]: session.commit()

构建Foo实例

In [9]: foo = session.query(Foo).first()
Foo load

In [10]: session.rollback()

会话中找到的现已过期的实例已刷新并返回

In [11]: foo2 = session.query(Foo).first()
Foo refresh

两个名称都绑定到同一个实例

In [12]: foo is foo2
Out[12]: True

In [13]: session.rollback()

过期实例上的属性访问只会导致刷新

In [14]: foo.value
Foo refresh
Out[14]: 'test'