Sqlalchemy 在对象刷新时不会调用模型的重构器

Max*_*yuk 3 python sqlalchemy flask-sqlalchemy

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

\n\n

这是我的模型的示例:

\n\n
from app import db\nfrom sqlalchemy.orm import reconstructor\n\nt_model = db.Table(\n    \'models\', \n    db.metadata,\n    dbc.Column(\'id\', db.Integer, primary_key=True),\n    dbc.Column(\'value\', db.String)\n)\n\n\nclass Model(db.Model):\n\n  __table__ = t_order_data\n\n  def __init__(self, value)\n      self.value = value\n\n  @reconstructor\n  def init_on_load(self)\n      """cast to None if value is empty string"""\n      self.value = None if not self.value else self.value\n\n  def run_db_function(self)\n      try:\n          result = db.session.execute(db.func.somefunc(\'some_param\')).fetchone()\n      except Exception:\n          db.session.rollback()\n          raise\n      else:\n          db.session.commit()\n          return result\n
Run Code Online (Sandbox Code Playgroud)\n\n

我的代码做了什么:

\n\n
model = Model.query.get(1)  # in table this row is id=1, value=\'\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
model.value  # returns None -- OK\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我执行功能

\n\n
model.run_db_function()\n
Run Code Online (Sandbox Code Playgroud)\n\n

它运行数据库的例程,然后提交事务。

\n\n

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

\n\n

但这个时间值不会被转换

\n\n
model.value  # returns \'\' -- NOT OK\n
Run Code Online (Sandbox Code Playgroud)\n\n

sqlalchqmy ORM 的官方文档关于reconstructor装饰器的说明:

\n\n
\n

将一个方法指定为 \xe2\x80\x9creconstructor\xe2\x80\x9d,这是一个__init__类似方法,在从数据库加载实例或以其他方式重构实例后,ORM 将调用该方法。

\n
\n\n

从这个描述来看,我认为init_on_load也应该在重新获取后调用,但它没有发生。

\n\n

这是错误还是设计行为?如果这是正常行为,我应该如何进行?

\n

Ilj*_*ilä 5

引用官方文档:

\n\n
\n

reconstructor()是进入更大的 \xe2\x80\x9cinstance level\xe2\x80\x9d 事件系统的快捷方式,可以使用事件 API 进行订阅 - 请参阅 获取InstanceEvents这些事件的完整 API 描述。

\n
\n\n

本质上reconstructor()是一条捷径InstanceEvents.load

\n\n
\n

在通过 创建对象实例__new__并发生初始属性填充后接收对象实例。\n ...\n 这通常在基于传入结果行创建实例时发生,并且仅对该实例调用一次\xe2 \x80\x99s 生命周期

\n
\n\n

当您提交或回滚时,默认设置是使会话中找到的实例的所有 ORM 控制属性过期。实例本身仍然存在。当您重新获取与会话中找到的实例匹配的行时,它会被刷新。一个例子:

\n\n
In [2]: from sqlalchemy.events import event\n\nIn [3]: class Foo(Base):\n   ...:     id = Column(Integer, primary_key=True, autoincrement=True)\n   ...:     value = Column(Unicode)\n   ...:     __tablename__ = \'foo\'\n   ...:     \n
Run Code Online (Sandbox Code Playgroud)\n\n

添加事件监听器,以便我们可以跟踪正在发生的事情

\n\n
In [5]: @event.listens_for(Foo, \'load\')\n   ...: def foo_load(target, context):\n   ...:     print(\'Foo load\')\n   ...:     \n\nIn [6]: @event.listens_for(Foo, \'refresh\')\n   ...: def foo_load(target, context, attrs):\n   ...:     print(\'Foo refresh\')\n   ...:     \n\nIn [7]: session.add(Foo(value=\'test\'))\n\nIn [8]: session.commit()\n
Run Code Online (Sandbox Code Playgroud)\n\n

Foo 实例已构造

\n\n
In [9]: foo = session.query(Foo).first()\nFoo load\n\nIn [10]: session.rollback()\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
In [11]: foo2 = session.query(Foo).first()\nFoo refresh\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
In [12]: foo is foo2\nOut[12]: True\n\nIn [13]: session.rollback()\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
In [14]: foo.value\nFoo refresh\nOut[14]: \'test\'\n
Run Code Online (Sandbox Code Playgroud)\n