SqlAlchemy 2.0 中文文档(七十四)(3)https://developer.aliyun.com/article/1562363
关键行为变化 - ORM
after_rollback() Session 事件现在在对象过期之前触发
SessionEvents.after_rollback()
事件现在可以访问对象在其状态被过期之前的属性状态(例如“快照移除”)。这使得该事件与SessionEvents.after_commit()
事件的行为保持一致,后者在“快照”被移除之前也会触发:
sess = Session() user = sess.query(User).filter_by(name="x").first() @event.listens_for(sess, "after_rollback") def after_rollback(session): # 'user.name' is now present, assuming it was already # loaded. previously this would raise upon trying # to emit a lazy load. print("user name: %s" % user.name) @event.listens_for(sess, "after_commit") def after_commit(session): # 'user.name' is present, assuming it was already # loaded. this is the existing behavior. print("user name: %s" % user.name) if should_rollback: sess.rollback() else: sess.commit()
请注意,Session
仍将禁止在此事件中发出 SQL;这意味着未加载的属性仍无法在事件范围内加载。
#3934 ### 修复了与 select_from()
一起使用单表继承的问题
当生成 SQL 时,Query.select_from()
方法现在会尊重单表继承列鉴别器;之前,只有查询列列表中的表达式会被考虑。
假设 Manager
是 Employee
的子类。像下面这样的查询:
sess.query(Manager.id)
会生成如下 SQL:
SELECT employee.id FROM employee WHERE employee.type IN ('manager')
然而,如果 Manager
只是通过 Query.select_from()
指定而不在列列表中,鉴别器将不会被添加:
sess.query(func.count(1)).select_from(Manager)
会生成:
SELECT count(1) FROM employee
通过修复,Query.select_from()
现在可以正常工作,我们得到:
SELECT count(1) FROM employee WHERE employee.type IN ('manager')
可能一直通过手动提供 WHERE 子句来解决此问题的应用程序可能需要进行调整。
#3891 ### 在替换时不再改变先前集合
ORM 在映射集合成员更改时会发出事件。将集合分配给将替换先前集合的属性时,这样做的一个副作用是,被替换的集合也会被改变,这是误导性的和不必要的:
>>> a1, a2, a3 = Address("a1"), Address("a2"), Address("a3") >>> user.addresses = [a1, a2] >>> previous_collection = user.addresses # replace the collection with a new one >>> user.addresses = [a2, a3] >>> previous_collection [Address('a1'), Address('a2')]
在更改之前,previous_collection
将删除“a1”成员,对应于不再在新集合中的成员。
#3913 ### 在批量集合设置之前,@validates 方法会接收所有值
使用 @validates
的方法现在在“批量设置”操作期间会接收集合的所有成员,然后再应用比较到现有集合上。
给定一个映射如下:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B") @validates("bs") def convert_dict_to_b(self, key, value): return B(data=value["data"]) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) data = Column(String)
在上面的示例中,我们可以如下使用验证器,将传入的字典转换为 B
的实例并在集合附加时使用:
a1 = A() a1.bs.append({"data": "b1"})
然而,集合赋值会失败,因为 ORM 会假定传入的对象已经是 B
的实例,因此在进行集合附加之前会尝试将它们与集合的现有成员进行比较,而实际上是在执行调用验证器的集合附加之前。这将使得批量设置操作无法适应需要事先修改的非 ORM 对象,比如字典:
a1 = A() a1.bs = [{"data": "b1"}]
新逻辑使用新的 AttributeEvents.bulk_replace()
事件,以确保所有值都会被提前发送到 @validates
函数。
作为这一变化的一部分,这意味着验证器现在将在批量设置时接收所有集合成员,而不仅仅是新成员。假设一个简单的验证器如下:
class A(Base): # ... @validates("bs") def validate_b(self, key, value): assert value.data is not None return value
如果我们开始的集合如下:
a1 = A() b1, b2 = B(data="one"), B(data="two") a1.bs = [b1, b2]
然后,用与第一个重叠的集合替换了原集合:
b3 = B(data="three") a1.bs = [b2, b3]
以前,第二次赋值只会触发一次A.validate_b
方法,对于b3
对象。b2
对象将被视为已经存在于集合中且不会被验证。使用新行为,b2
和b3
都会在传递到集合之前传递给A.validate_b
。因此,验证方法必须具有幂等行为以适应这种情况。
另请参阅
新的批量替换事件
#3896 ### 使用 flag_dirty()标记对象为“脏”而不更改任何属性
如果flag_modified()
函数用于标记未加载的属性为已修改,则会引发异常:
a1 = A(data="adf") s.add(a1) s.flush() # expire, similarly as though we said s.commit() s.expire(a1, "data") # will raise InvalidRequestError attributes.flag_modified(a1, "data")
这是因为如果属性在刷新发生时仍未出现,那么刷新过程很可能会失败。要将对象标记为“已修改”而不指定任何特定属性,以便在自定义事件处理程序(如SessionEvents.before_flush()
)中考虑到刷新过程中,使用新的flag_dirty()
函数:
from sqlalchemy.orm import attributes attributes.flag_dirty(a1)
#3753 ### 从 scoped_session 中移除“scope”关键字
一个非常古老且未记录的关键字参数scope
已被移除:
from sqlalchemy.orm import scoped_session Session = scoped_session(sessionmaker()) session = Session(scope=None)
该关键字的目的是尝试允许变量“作用域”,其中None
表示“无作用域”,因此会返回一个新的Session
。该关键字从未被记录在案,如果遇到将会引发TypeError
。预计该关键字未被使用,但如果用户在测试期间报告与此相关的问题,可以通过弃用来恢复。
#3796 ### 与 onupdate 一起对 post_update 进行细化
使用relationship.post_update
功能的关系现在将与设置了Column.onupdate
值的列更好地交互。如果插入对象时为列显式指定了值,则在 UPDATE 期间会重新声明该值,以便“onupdate”规则不会覆盖它:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) favorite_b_id = Column(ForeignKey("b.id", name="favorite_b_fk")) bs = relationship("B", primaryjoin="A.id == B.a_id") favorite_b = relationship( "B", primaryjoin="A.favorite_b_id == B.id", post_update=True ) updated = Column(Integer, onupdate=my_onupdate_function) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id", name="a_fk")) a1 = A() b1 = B() a1.bs.append(b1) a1.favorite_b = b1 a1.updated = 5 s.add(a1) s.flush()
在上述情况下,以前的行为是 UPDATE 会在 INSERT 之后发出,从而触发“onupdate”并覆盖值“5”。现在的 SQL 如下:
INSERT INTO a (favorite_b_id, updated) VALUES (?, ?) (None, 5) INSERT INTO b (a_id) VALUES (?) (1,) UPDATE a SET favorite_b_id=?, updated=? WHERE a.id = ? (1, 5, 1)
此外,如果“updated”的值未设置,则我们将正确地在a1.updated
上获取新生成的值;以前,刷新或过期属性以允许生成的值存在的逻辑不会为 post-update 触发。在这种情况下,当刷新中发生 flush 时,也会发出InstanceEvents.refresh_flush()
事件。
#3472 ### post_update 与 ORM 版本控制集成
post_update 功能,文档化在指向自身的行 / 相互依赖的行,涉及对特定关系绑定外键的更改发出 UPDATE 语句,除了针对目标行通常会发出的 INSERT/UPDATE/DELETE。此 UPDATE 语句现在参与版本控制功能,文档化在配置版本计数器。
给定一个映射:
class Node(Base): __tablename__ = "node" id = Column(Integer, primary_key=True) version_id = Column(Integer, default=0) parent_id = Column(ForeignKey("node.id")) favorite_node_id = Column(ForeignKey("node.id")) nodes = relationship("Node", primaryjoin=remote(parent_id) == id) favorite_node = relationship( "Node", primaryjoin=favorite_node_id == remote(id), post_update=True ) __mapper_args__ = {"version_id_col": version_id}
更新将另一个节点关联为“favorite”的节点的 UPDATE 现在也会增加版本计数器,并匹配当前版本:
node = Node() session.add(node) session.commit() # node is now version #1 node = session.query(Node).get(node.id) node.favorite_node = Node() session.commit() # node is now version #2
请注意,这意味着一个对象由于其他属性的更改而接收到一个 UPDATE,以及由于 post_update 关系更改而接收到第二个 UPDATE,现在将会为一个 flush 接收到两个版本计数器更新。但是,如果对象在当前 flush 中受到 INSERT 的影响,则版本计数器不会额外增加一次,除非存在服务器端版本控制方案。
现在讨论 post_update 为什么即使是 UPDATE 也会发出 UPDATE 的原因在为什么 post_update 除了第一个 UPDATE 还会发出 UPDATE?。
另请参阅
指向自身的行 / 相互依赖的行
为什么 post_update 除了第一个 UPDATE 还会发出 UPDATE?
#3496 ### 在对象过期之前,after_rollback() 会发出 Session 事件
SessionEvents.after_rollback()
事件现在可以在对象状态被过期之前访问属性状态(例如“快照移除”)。这使得事件与SessionEvents.after_commit()
事件的行为一致,后者也会在“快照”被移除之前发出:
sess = Session() user = sess.query(User).filter_by(name="x").first() @event.listens_for(sess, "after_rollback") def after_rollback(session): # 'user.name' is now present, assuming it was already # loaded. previously this would raise upon trying # to emit a lazy load. print("user name: %s" % user.name) @event.listens_for(sess, "after_commit") def after_commit(session): # 'user.name' is present, assuming it was already # loaded. this is the existing behavior. print("user name: %s" % user.name) if should_rollback: sess.rollback() else: sess.commit()
请注意,Session
仍将禁止在此事件中发出 SQL;这意味着未加载的属性仍将无法在事件范围内加载。
修复了与select_from()
一起使用单表继承的问题
Query.select_from()
方法现在在生成 SQL 时尊重单表继承列鉴别器;以前,只有查询列列表中的表达式会被考虑进去。
假设Manager
是Employee
的子类。像下面这样的查询:
sess.query(Manager.id)
将生成的 SQL 如下:
SELECT employee.id FROM employee WHERE employee.type IN ('manager')
然而,如果Manager
仅由Query.select_from()
指定,而不在列列表中,那么鉴别器将不会被添加:
sess.query(func.count(1)).select_from(Manager)
将生成:
SELECT count(1) FROM employee
通过修复,Query.select_from()
现在可以正常工作,我们得到:
SELECT count(1) FROM employee WHERE employee.type IN ('manager')
可能一直通过手动提供 WHERE 子句来解决此问题的应用程序可能需要进行调整。
在替换���不再改变先前集合
ORM 在映射集合的成员发生变化时会发出事件。将集合分配给将替换先前集合的属性时,这样做的一个副作用是,被替换的集合也会被改变,这是误导性和不必要的:
>>> a1, a2, a3 = Address("a1"), Address("a2"), Address("a3") >>> user.addresses = [a1, a2] >>> previous_collection = user.addresses # replace the collection with a new one >>> user.addresses = [a2, a3] >>> previous_collection [Address('a1'), Address('a2')]
在上面的示例中,在更改之前,previous_collection
将删除“a1”成员,对应于不再在新集合中的成员。
在比较之前,@validates 方法会接收批量集合设置的所有值
使用@validates
的方法现在在“批量设置”操作期间将接收集合的所有成员,然后再将比较应用于现有集合。
给定一个映射如下:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) bs = relationship("B") @validates("bs") def convert_dict_to_b(self, key, value): return B(data=value["data"]) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) data = Column(String)
在上面的示例中,我们可以使用验证器如下,将传入的字典转换为B
的实例进行集合附加:
a1 = A() a1.bs.append({"data": "b1"})
然而,集合赋值将失败,因为 ORM 会假定传入的对象已经是B
的实例,因为它在尝试将它们与集合的现有成员进行比较之前,会执行集合附加操作,这实际上会调用验证器。这将使得批量设置操作无法容纳像需要事先修改的字典这样的非 ORM 对象:
a1 = A() a1.bs = [{"data": "b1"}]
新逻辑使用新的AttributeEvents.bulk_replace()
事件,以确保所有值都被提前发送到@validates
函数。
作为这一变化的一部分,现在验证器将在批量设置时接收所有集合成员,而不仅仅是新成员。假设一个简单的验证器如下:
class A(Base): # ... @validates("bs") def validate_b(self, key, value): assert value.data is not None return value
如上所述,如果我们从一个集合开始:
a1 = A() b1, b2 = B(data="one"), B(data="two") a1.bs = [b1, b2]
然后,用一个与第一个重叠的集合替换了该集合:
b3 = B(data="three") a1.bs = [b2, b3]
以前,第二次赋值只会触发一次A.validate_b
方法,对于b3
对象。b2
对象将被视为已经存在于集合中并且不会被验证。通过新行为,b2
和b3
都会在传递到集合之前传递给A.validate_b
。因此,验证方法必须采用幂等行为以适应这种情况。
另请参阅
新的 bulk_replace 事件
使用 flag_dirty()将对象标记为“dirty”而不改变任何属性
如果使用flag_modified()
函数标记一个未加载的属性为已修改,现在会引发异常:
a1 = A(data="adf") s.add(a1) s.flush() # expire, similarly as though we said s.commit() s.expire(a1, "data") # will raise InvalidRequestError attributes.flag_modified(a1, "data")
这是因为如果属性在刷新发生时仍未出现,则刷新过程很可能会失败。要将对象标记为“修改”,而不是特指任何属性,以便考虑到自定义事件处理程序(如SessionEvents.before_flush()
)的刷新过程中,使用新的flag_dirty()
函数:
from sqlalchemy.orm import attributes attributes.flag_dirty(a1)
从 scoped_session 中移除了“scope”关键字
一个非常古老且未记录的关键字参数scope
已被移除:
from sqlalchemy.orm import scoped_session Session = scoped_session(sessionmaker()) session = Session(scope=None)
此关键字的目的是尝试允许变量“scopes”,其中None
表示“无范围”,因此将返回一个新的Session
。该关键字从未被记录在案,如果遇到将引发TypeError
。预计不会使用此关键字,但如果用户在测试期间报告与此相关的问题,则可以通过弃用来恢复。
与 onupdate 一起的 post_update 的改进
使用relationship.post_update
功能的关系现在将更好地与设置了Column.onupdate
值的列交互。如果插入对象时为列显式指定了值,则在 UPDATE 期间将重新声明该值,以便“onupdate”规则不会覆盖它:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) favorite_b_id = Column(ForeignKey("b.id", name="favorite_b_fk")) bs = relationship("B", primaryjoin="A.id == B.a_id") favorite_b = relationship( "B", primaryjoin="A.favorite_b_id == B.id", post_update=True ) updated = Column(Integer, onupdate=my_onupdate_function) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id", name="a_fk")) a1 = A() b1 = B() a1.bs.append(b1) a1.favorite_b = b1 a1.updated = 5 s.add(a1) s.flush()
在上述情况下,以前的行为是在插入之后会发出更新,从而触发“onupdate”,并覆盖值“5”。现在的 SQL 如下:
INSERT INTO a (favorite_b_id, updated) VALUES (?, ?) (None, 5) INSERT INTO b (a_id) VALUES (?) (1,) UPDATE a SET favorite_b_id=?, updated=? WHERE a.id = ? (1, 5, 1)
此外,如果“updated”的值 未 设置,则我们将在 a1.updated
上正确地获得新生成的值;以前,刷新或过期属性的逻辑以允许生成的值存在将不会触发 post-update。在此情况下,还会在刷新期间发出 InstanceEvents.refresh_flush()
事件。
post_update 与 ORM 版本控制集成
post_update 功能,记录在 指向自身的行 / 相互依赖的行,涉及响应特定关系绑定外键的更改而发出 UPDATE 语句,除了为目标行正常发出的 INSERT/UPDATE/DELETE 外。此 UPDATE 语句现在参与版本控制功能,记录在 配置版本计数器。
给定一个映射:
class Node(Base): __tablename__ = "node" id = Column(Integer, primary_key=True) version_id = Column(Integer, default=0) parent_id = Column(ForeignKey("node.id")) favorite_node_id = Column(ForeignKey("node.id")) nodes = relationship("Node", primaryjoin=remote(parent_id) == id) favorite_node = relationship( "Node", primaryjoin=favorite_node_id == remote(id), post_update=True ) __mapper_args__ = {"version_id_col": version_id}
将另一个节点关联为“favorite”的节点进行更新将会增加版本计数器并匹配当前版本:
node = Node() session.add(node) session.commit() # node is now version #1 node = session.query(Node).get(node.id) node.favorite_node = Node() session.commit() # node is now version #2
请注意,这意味着对象在响应其他属性更改而接收到 UPDATE,并在 post_update 关系更改导致第二个 UPDATE 时,现在将收到一次刷新两次版本计数器更新。但是,如果对象在当前刷新中接受到 INSERT,则版本计数器 不会 再次增加,除非存在服务器端版本控制方案。
现在讨论了 post_update 为何即使是对 UPDATE 也会发出 UPDATE 的原因 为什么 post_update 除了第一个 UPDATE 还会发出 UPDATE?。
另请参阅
指向自身的行 / 相互依赖的行
为什么 post_update 除了第一个 UPDATE 还会发出 UPDATE?
SqlAlchemy 2.0 中文文档(七十四)(5)https://developer.aliyun.com/article/1562367