SqlAlchemy 2.0 中文文档(七十四)(4)

简介: SqlAlchemy 2.0 中文文档(七十四)

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() 方法现在会尊重单表继承列鉴别器;之前,只有查询列列表中的表达式会被考虑。

假设 ManagerEmployee 的子类。像下面这样的查询:

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对象将被视为已经存在于集合中且不会被验证。使用新行为,b2b3都会在传递到集合之前传递给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()事件。

#3471

#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;这意味着未加载的属性仍将无法在事件范围内加载。

#3934

修复了与select_from()一起使用单表继承的问题

Query.select_from()方法现在在生成 SQL 时尊重单表继承列鉴别器;以前,只有查询列列表中的表达式会被考虑进去。

假设ManagerEmployee的子类。像下面这样的查询:

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对象将被视为已经存在于集合中并且不会被验证。通过新行为,b2b3都会在传递到集合之前传递给A.validate_b。因此,验证方法必须采用幂等行为以适应这种情况。

另请参阅

新的 bulk_replace 事件

#3896

使用 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)

#3753

从 scoped_session 中移除了“scope”关键字

一个非常古老且未记录的关键字参数scope已被移除:

from sqlalchemy.orm import scoped_session
Session = scoped_session(sessionmaker())
session = Session(scope=None)

此关键字的目的是尝试允许变量“scopes”,其中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()

在上述情况下,以前的行为是在插入之后会发出更新,从而触发“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() 事件。

#3471

#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”的节点进行更新将会增加版本计数器并匹配当前版本:

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?

#3496


SqlAlchemy 2.0 中文文档(七十四)(5)https://developer.aliyun.com/article/1562367

相关文章
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十四)(5)
SqlAlchemy 2.0 中文文档(七十四)
46 6
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十四)(3)
SqlAlchemy 2.0 中文文档(七十四)
43 1
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(六十八)(3)
SqlAlchemy 2.0 中文文档(六十八)
23 0
|
3月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(七十四)(2)
SqlAlchemy 2.0 中文文档(七十四)
32 1
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十四)(1)
SqlAlchemy 2.0 中文文档(七十四)
60 1
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(1)
SqlAlchemy 2.0 中文文档(七十六)
38 2
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(2)
SqlAlchemy 2.0 中文文档(七十六)
63 2
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(四十三)(3)
SqlAlchemy 2.0 中文文档(四十三)
26 0
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(六十五)(3)
SqlAlchemy 2.0 中文文档(六十五)
24 0
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(六十五)(1)
SqlAlchemy 2.0 中文文档(六十五)
30 0