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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: SqlAlchemy 2.0 中文文档(七十四)

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


介绍

本指南介绍了 SQLAlchemy 版本 1.2 中的新功能,并记录了影响从 SQLAlchemy 1.1 系列迁移其应用程序的用户的更改。

请仔细查看行为变化部分,可能会对行为产生不兼容的变化。

平台支持

针对 Python 2.7 及更高版本

SQLAlchemy 1.2 现在将最低 Python 版本提升至 2.7,不再支持 2.6。预计将合并到 1.2 系列中的新语言特性在  Python 2.6 中不受支持。对于 Python 3 的支持,SQLAlchemy 目前在 3.5 和 3.6 版本上进行测试。

针对 Python 2.7 及更高版本

SQLAlchemy 1.2 现在将最低 Python 版本提升至 2.7,不再支持 2.6。预计将合并到 1.2 系列中的新语言特性在  Python 2.6 中不受支持。对于 Python 3 的支持,SQLAlchemy 目前在 3.5 和 3.6 版本上进行测试。

新功能和改进 - ORM

“Baked” 加载现在是懒加载的默认选项

sqlalchemy.ext.baked 扩展首次引入于 1.0 系列,允许构建所谓的BakedQuery对象,该对象与表示查询结构的缓存键一起生成Query对象;然后将此缓存键链接到生成的字符串 SQL 语句,以便后续使用具有相同结构的另一个BakedQuery将绕过构建Query对象、构建其中的核心select()对象,以及将select()编译为字符串的所有开销,从而削减通常与构建和发出 ORM Query对象相关的大部分函数调用开销。

BakedQuery 现在在 ORM 默认情况下用于生成“延迟”查询,用于懒加载relationship()构造,例如默认的lazy="select"关系加载策略。这将显著减少应用程序在使用懒加载查询加载集合和相关对象时的函数调用。此功能以前在 1.0 和 1.1 中通过使用全局 API 方法或使用baked_select策略可用,现在是此行为的唯一实现。该功能还得到改进,使得对于具有懒加载后生效的其他加载器选项的对象仍然可以进行缓存。

可以使用 relationship.bake_queries 标志在每个关系基础上禁用缓存行为,这对于非常罕见的情况非常有用,例如使用不兼容缓存的自定义 Query 实现的关系。

#3954 ### 新的“selectin”急加载,一次性使用 IN 加载所有集合

添加了一个名为“selectin”加载的新急加载器,这在许多方面类似于“子查询”加载,但生成的 SQL 语句更简单,可缓存且更高效。

给定以下查询:

q = (
    session.query(User)
    .filter(User.name.like("%ed%"))
    .options(subqueryload(User.addresses))
)

生成的 SQL 将是针对 User 的查询,然后是 User.addresses 的 subqueryload(请注意还列出了参数):

SELECT  users.id  AS  users_id,  users.name  AS  users_name
FROM  users
WHERE  users.name  LIKE  ?
('%ed%',)
SELECT  addresses.id  AS  addresses_id,
  addresses.user_id  AS  addresses_user_id,
  addresses.email_address  AS  addresses_email_address,
  anon_1.users_id  AS  anon_1_users_id
FROM  (SELECT  users.id  AS  users_id
FROM  users
WHERE  users.name  LIKE  ?)  AS  anon_1
JOIN  addresses  ON  anon_1.users_id  =  addresses.user_id
ORDER  BY  anon_1.users_id
('%ed%',)

使用“selectin”加载,我们实际上得到了一个引用父查询中加载的实际主键值的 SELECT:

q = (
    session.query(User)
    .filter(User.name.like("%ed%"))
    .options(selectinload(User.addresses))
)

产生:

SELECT  users.id  AS  users_id,  users.name  AS  users_name
FROM  users
WHERE  users.name  LIKE  ?
('%ed%',)
SELECT  users_1.id  AS  users_1_id,
  addresses.id  AS  addresses_id,
  addresses.user_id  AS  addresses_user_id,
  addresses.email_address  AS  addresses_email_address
FROM  users  AS  users_1
JOIN  addresses  ON  users_1.id  =  addresses.user_id
WHERE  users_1.id  IN  (?,  ?)
ORDER  BY  users_1.id
(1,  3)

上述 SELECT 语句包括以下优点:

  • 它不使用子查询,只是一个 INNER JOIN,这意味着在像 MySQL 这样不喜欢子查询的数据库上性能会更好
  • 其结构独立于原始查询;与新的 扩展 IN 参数系统 结合使用,我们在大多数情况下可以使用“烘焙”查询来缓存字符串 SQL,从而显著减少每个查询的开销。
  • 因为查询仅获取给定主键标识符列表,“selectin”加载可能与 Query.yield_per() 兼容,以便一次处理 SELECT 结果的块,前提是数据库驱动程序允许多个同时游标(SQLite、PostgreSQL;是 MySQL 驱动程序或 SQL Server ODBC 驱动程序)。联接急加载和子查询急加载都不兼容 Query.yield_per()

选择急加载的缺点可能是潜在的大型 SQL 查询,带有大量的 IN 参数列表。 IN 参数列表本身被分组为 500 个一组,因此超过 500  个结果对象的结果集将有更多额外的“SELECT IN”查询。此外,对复合主键的支持取决于数据库是否能够使用带有 IN 的元组,例如 (table.column_one, table_column_two) IN ((?, ?), (?, ?) (?, ?))。目前,已知 PostgreSQL 和 MySQL 兼容此语法,而 SQLite 不兼容。

另请参见

Select IN 加载

#3944 ### “selectin” 多态加载,使用单独的 IN 查询加载子类

与刚刚在新“selectin” eager loading, loads all collections at once using IN   中描述的“selectin”关系加载功能类似的是“selectin”多态加载。这是一种专为连接式的急加载定制的多态加载功能,允许基本实体的加载通过简单的  SELECT 语句进行,但是额外的子类属性使用额外的 SELECT 语句加载:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> query = session.query(Employee).options(
...     selectin_polymorphic(Employee, [Manager, Engineer])
... )
>>> query.all()
SELECT
  employee.id  AS  employee_id,
  employee.name  AS  employee_name,
  employee.type  AS  employee_type
FROM  employee
()
SELECT
  engineer.id  AS  engineer_id,
  employee.id  AS  employee_id,
  employee.type  AS  employee_type,
  engineer.engineer_name  AS  engineer_engineer_name
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
(1,  2)
SELECT
  manager.id  AS  manager_id,
  employee.id  AS  employee_id,
  employee.type  AS  employee_type,
  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
(3,) 

另请参阅

使用 selectin_polymorphic()

#3948 ### 可接收临时 SQL 表达式的 ORM 属性

新增了一个 ORM 属性类型query_expression(),与deferred()类似,但其 SQL 表达式在查询时确定,使用新选项with_expression();如果未指定,属性默认为None

from sqlalchemy.orm import query_expression
from sqlalchemy.orm import with_expression
class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    x = Column(Integer)
    y = Column(Integer)
    # will be None normally...
    expr = query_expression()
# but let's give it x + y
a1 = session.query(A).options(with_expression(A.expr, A.x + A.y)).first()
print(a1.expr)

另请参阅

查询时的 SQL 表达式作为映射属性

#3058 ### ORM 支持多表删除

ORM Query.delete() 方法支持多表条件的删除,如多表条件支持的删除中所介绍的。该功能的工作方式与更新的多表条件相同,最初在 0.8 版本中引入,并在 Query.update()支持 UPDATE…FROM 中描述。

下面,我们对SomeEntity发出一个 DELETE 请求,添加一个 FROM 子句(或者等效的,根据后端不同)对SomeOtherEntity进行操作:

query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter(
    SomeOtherEntity.foo == "bar"
).delete()

另请参阅

多表条件支持的删除

#959 ### 支持混合、复合的批量更新

现在,混合属性(例如sqlalchemy.ext.hybrid)以及复合属性(复合列类型)在使用Query.update()更新语句的 SET 子句中均得到支持。

对于混合属性,可以直接使用简单的表达式,或者可以使用新的装饰器hybrid_property.update_expression()将值拆分为多个列/表达式:

class Person(Base):
    # ...
    first_name = Column(String(10))
    last_name = Column(String(10))
    @hybrid.hybrid_property
    def name(self):
        return self.first_name + " " + self.last_name
    @name.expression
    def name(cls):
        return func.concat(cls.first_name, " ", cls.last_name)
    @name.update_expression
    def name(cls, value):
        f, l = value.split(" ", 1)
        return [(cls.first_name, f), (cls.last_name, l)]

如上所述,可以使用以下方式渲染 UPDATE:

session.query(Person).filter(Person.id == 5).update({Person.name: "Dr. No"})

类似的功能也适用于复合属性,其中复合值将被拆分为其各个列以进行批量更新:

session.query(Vertex).update({Edge.start: Point(3, 4)})

另请参阅

允许批量 ORM 更新 ### 混合属性支持在子类之间重用,重新定义@getter

sqlalchemy.ext.hybrid.hybrid_property类现在支持在子类之间多次调用像@setter@expression等的变异器,并且现在提供了一个@getter变异器,以便特定的混合属性可以在子类或其他类之间重新使用。这与标准 Python 中@property的行为类似:

class FirstNameOnly(Base):
    # ...
    first_name = Column(String)
    @hybrid_property
    def name(self):
        return self.first_name
    @name.setter
    def name(self, value):
        self.first_name = value
class FirstNameLastName(FirstNameOnly):
    # ...
    last_name = Column(String)
    @FirstNameOnly.name.getter
    def name(self):
        return self.first_name + " " + self.last_name
    @name.setter
    def name(self, value):
        self.first_name, self.last_name = value.split(" ", maxsplit=1)
    @name.expression
    def name(cls):
        return func.concat(cls.first_name, " ", cls.last_name)

在上面的示例中,FirstNameOnly.name混合属性被FirstNameLastName子类引用,以便将其专门用于新子类。这是通过在每次调用@getter@setter以及所有其他变异器方法像@expression中复制混合对象到新对象来实现的,从而保持先前混合属性的定义不变。以前,像@setter这样的方法会直接修改现有的混合属性,干扰了超类上的定义。

注意

请务必阅读在子类之间重用混合属性的文档,了解如何覆盖hybrid_property.expression()hybrid_property.comparator()的重要注意事项,因为在某些情况下,可能需要使用特殊限定符hybrid_property.overrides来避免与QueryableAttribute发生名称冲突。

注意

@hybrid_property中的这种变化意味着,当向@hybrid_property添加 setter 和其他状态时,方法必须保留原始混合属性的名称,否则具有附加状态的新混合属性将以不匹配的名称存在于类中。这与标准 Python 中的@property构造的行为相同:

class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name
    # WRONG - will raise AttributeError: can't set attribute when
    # assigning to .name
    @name.setter
    def _set_name(self, value):
        self.first_name = value
class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name
    # CORRECT - note regular Python @property works the same way
    @name.setter
    def name(self, value):
        self.first_name = value

#3911

#3912 ### 新的 bulk_replace 事件

为了适应 A @validates method receives all values on bulk-collection set before comparison 中描述的验证用例,添加了一个新的AttributeEvents.bulk_replace()方法,该方法与AttributeEvents.append()AttributeEvents.remove()事件一起调用。在比较现有集合之前调用“bulk_replace”,以便可以修改集合。之后,单个项目将附加到新的目标集合,触发为集合中的新项目触发的“append”事件,这与以前的行为相同。下面同时说明了“bulk_replace”和“append”,包括如果使用集合赋值,“append”将接收到已由“bulk_replace”处理的对象。新符号attributes.OP_BULK_REPLACE可以用于确定此“append”事件是否是批量替换的第二部分:

from sqlalchemy.orm.attributes import OP_BULK_REPLACE
@event.listens_for(SomeObject.collection, "bulk_replace")
def process_collection(target, values, initiator):
    values[:] = [_make_value(value) for value in values]
@event.listens_for(SomeObject.collection, "append", retval=True)
def process_collection(target, value, initiator):
    # make sure bulk_replace didn't already do it
    if initiator is None or initiator.op is not OP_BULK_REPLACE:
        return _make_value(value)
    else:
        return value

#3896 ### 为 sqlalchemy.ext.mutable 添加了新的“modified”事件处理程序

添加了一个新的事件处理程序AttributeEvents.modified(),该处理程序在对flag_modified()方法的调用时触发,通常是从sqlalchemy.ext.mutable扩展调用的。

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy import event
Base = declarative_base()
class MyDataClass(Base):
    __tablename__ = "my_data"
    id = Column(Integer, primary_key=True)
    data = Column(MutableDict.as_mutable(JSONEncodedDict))
@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance):
    print("json value modified:", instance.data)

上述事件处理程序将在对.data字典进行就地更改时触发。

#3303 ### 添加了对 Session.refresh 的“for update”参数

添加了新参数Session.refresh.with_for_updateSession.refresh()方法。当Query.with_lockmode()方法被弃用,而倾向于Query.with_for_update()时,Session.refresh()方法从未更新以反映新选项:

session.refresh(some_object, with_for_update=True)

Session.refresh.with_for_update参数现在接受一个选项字典,该字典将作为发送给Query.with_for_update()的相同参数:

session.refresh(some_objects, with_for_update={"read": True})

新参数取代了Session.refresh.lockmode参数。

#3991 ### 就地变异操作符适用于 MutableSet、MutableList

对于MutableSet,我们实现了就地变异操作符__ior____iand____ixor____isub__,以及对于MutableList__iadd__。虽然这些方法以前可以成功地更新集合,但它们不会正确地触发更改事件。这些操作符像以前一样改变集合,但额外地发出了正确的更改事件,以便更改成为下一个刷新进程的一部分:

model = session.query(MyModel).first()
model.json_set &= {1, 3}

#3853 ### AssociationProxy any()、has()、contains()可以与链式关联代理一起使用

AssociationProxy.any()AssociationProxy.has()AssociationProxy.contains()比较方法现在支持链接到一个属性,该属性本身也是一个AssociationProxy,递归地。在下面的示例中,A.b_values是一个关联到AtoB.bvalue的关联代理,它本身是一个关联到B的关联代理:

class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    b_values = association_proxy("atob", "b_value")
    c_values = association_proxy("atob", "c_value")
class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    value = Column(String)
    c = relationship("C")
class C(Base):
    __tablename__ = "c"
    id = Column(Integer, primary_key=True)
    b_id = Column(ForeignKey("b.id"))
    value = Column(String)
class AtoB(Base):
    __tablename__ = "atob"
    a_id = Column(ForeignKey("a.id"), primary_key=True)
    b_id = Column(ForeignKey("b.id"), primary_key=True)
    a = relationship("A", backref="atob")
    b = relationship("B", backref="atob")
    b_value = association_proxy("b", "value")
    c_value = association_proxy("b", "c")

我们可以使用AssociationProxy.contains()A.b_values上进行查询,以跨越两个代理A.b_valuesAtoB.b_value

>>> s.query(A).filter(A.b_values.contains("hi")).all()
SELECT  a.id  AS  a_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  atob
WHERE  a.id  =  atob.a_id  AND  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  atob.b_id  AND  b.value  =  :value_1))) 

类似地,我们可以使用AssociationProxy.any()A.c_values上进行查询,以跨越两个代理A.c_valuesAtoB.c_value

>>> s.query(A).filter(A.c_values.any(value="x")).all()
SELECT  a.id  AS  a_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  atob
WHERE  a.id  =  atob.a_id  AND  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  atob.b_id  AND  (EXISTS  (SELECT  1
FROM  c
WHERE  b.id  =  c.b_id  AND  c.value  =  :value_1))))) 

#3769 ### 标识键增强以支持分片

现在,ORM 使用的标识键结构包含了一个额外的成员,以便来自不同上下文的两个相同的主键可以共存于同一个标识映射中。

水平分片 中的示例已更新以说明此行为。该示例显示了一个分片类 WeatherLocation,引用一个依赖的 WeatherReport 对象,其中 WeatherReport 类映射到一个存储简单整数主键的表。来自不同数据库的两个 WeatherReport 对象可能具有相同的主键值。该示例现在说明了一个新的 identity_token 字段跟踪此差异,以便这两个对象可以共存于同一标识映射中:

tokyo = WeatherLocation("Asia", "Tokyo")
newyork = WeatherLocation("North America", "New York")
tokyo.reports.append(Report(80.0))
newyork.reports.append(Report(75))
sess = create_session()
sess.add_all([tokyo, newyork, quito])
sess.commit()
# the Report class uses a simple integer primary key.  So across two
# databases, a primary key will be repeated.  The "identity_token" tracks
# in memory that these two identical primary keys are local to different
# databases.
newyork_report = newyork.reports[0]
tokyo_report = tokyo.reports[0]
assert inspect(newyork_report).identity_key == (Report, (1,), "north_america")
assert inspect(tokyo_report).identity_key == (Report, (1,), "asia")
# the token representing the originating shard is also available directly
assert inspect(newyork_report).identity_token == "north_america"
assert inspect(tokyo_report).identity_token == "asia"

#4137 ### “烘焙”加载现在是延迟加载的默认设置

sqlalchemy.ext.baked 扩展首次在 1.0 系列中引入,允许构建所谓的 BakedQuery 对象,该对象生成一个与表示查询结构的缓存键相关联的 Query 对象;然后将此缓存键链接到生成的字符串 SQL 语句,以便后续使用具有相同结构的另一个 BakedQuery 将绕过构建 Query 对象的所有开销,构建其中的核心 select() 对象,以及将 select() 编译为字符串,从而削减通常与构建和发出 ORM Query 对象相关的大部分函数调用开销。

当 ORM 生成“延迟”查询以懒加载 relationship() 构造时,默认现在使用 BakedQuery,例如默认的 lazy="select" 关系加载器策略。这将显著减少应用程序使用延迟加载查询加载集合和相关对象时的函数调用数量。以前,此功能在 1.0 和 1.1 中通过使用全局 API 方法或使用 baked_select 策略可用,现在是此行为的唯一实现。该功能还得到改进,使得对于具有延迟加载后生效的其他加载器选项的对象仍然可以进行缓存。

可以使用 relationship.bake_queries 标志在每个关系基础上禁用缓存行为,这对于非常不寻常的情况非常有用,例如使用不兼容缓存的自定义 Query 实现的关系。

#3954

新的 “selectin” 急切加载,一次加载所有集合使用 IN

添加了一个名为 “selectin” 加载的新急切加载器,从许多方面来看,它类似于 “subquery” 加载,但是生成了一个更简单的可缓存的 SQL 语句,而且更有效率。

给定如下查询:

q = (
    session.query(User)
    .filter(User.name.like("%ed%"))
    .options(subqueryload(User.addresses))
)

生成的 SQL 将是针对 User 的查询,然后是 User.addresses 的 subqueryload(注意还列出了参数):

SELECT  users.id  AS  users_id,  users.name  AS  users_name
FROM  users
WHERE  users.name  LIKE  ?
('%ed%',)
SELECT  addresses.id  AS  addresses_id,
  addresses.user_id  AS  addresses_user_id,
  addresses.email_address  AS  addresses_email_address,
  anon_1.users_id  AS  anon_1_users_id
FROM  (SELECT  users.id  AS  users_id
FROM  users
WHERE  users.name  LIKE  ?)  AS  anon_1
JOIN  addresses  ON  anon_1.users_id  =  addresses.user_id
ORDER  BY  anon_1.users_id
('%ed%',)

使用 “selectin” 加载,我们得到���是一个 SELECT,它引用了在父查询中加载的实际主键值:

q = (
    session.query(User)
    .filter(User.name.like("%ed%"))
    .options(selectinload(User.addresses))
)

产生:

SELECT  users.id  AS  users_id,  users.name  AS  users_name
FROM  users
WHERE  users.name  LIKE  ?
('%ed%',)
SELECT  users_1.id  AS  users_1_id,
  addresses.id  AS  addresses_id,
  addresses.user_id  AS  addresses_user_id,
  addresses.email_address  AS  addresses_email_address
FROM  users  AS  users_1
JOIN  addresses  ON  users_1.id  =  addresses.user_id
WHERE  users_1.id  IN  (?,  ?)
ORDER  BY  users_1.id
(1,  3)

上述 SELECT 语句包括以下优点:

  • 它不使用子查询,只是一个 INNER JOIN,这意味着在像 MySQL 这样不喜欢子查询的数据库上性能会更好
  • 其结构独立于原始查询;与新的 扩展 IN 参数系统 结合,我们在大多数情况下可以使用 “baked” 查询来缓存字符串 SQL,显著减少每个查询的开销
  • 由于查询仅为给定的主键标识符列表获取数据,“selectin” 加载可能与 Query.yield_per() 兼容,以便一次操作 SELECT 结果的一部分,前提是数据库驱动程序允许多个同时游标(SQLite,PostgreSQL;是 MySQL 驱动程序或 SQL Server ODBC 驱动程序)。联接式急切加载和子查询急切加载都不兼容 Query.yield_per()

selectin 急切加载的缺点是潜在的大型 SQL 查询,具有大量的 IN 参数列表。 IN 参数列表本身被分组为 500  个一组,因此超过 500 个 lead 对象的结果集将有更多的附加 “SELECT IN”  查询。此外,对复合主键的支持取决于数据库是否能够使用带有 IN 的元组,例如 (table.column_one, table_column_two) IN ((?, ?), (?, ?) (?, ?))。目前,已知 PostgreSQL 和 MySQL 兼容此语法,SQLite 不兼容。

另请参见

选择 IN 加载

#3944

“selectin” 多态加载,使用单独的 IN 查询加载子类

与刚刚在新的“selectin”急加载,使用 IN  一次加载所有集合中描述的“selectin”关系加载功能类似的是“selectin”多态加载。这是一个主要针对连接式急加载的多态加载功能,允许基本实体的加载通过简单的  SELECT 语句进行,然后额外子类的属性通过额外的 SELECT 语句进行加载:

>>> from sqlalchemy.orm import selectin_polymorphic
>>> query = session.query(Employee).options(
...     selectin_polymorphic(Employee, [Manager, Engineer])
... )
>>> query.all()
SELECT
  employee.id  AS  employee_id,
  employee.name  AS  employee_name,
  employee.type  AS  employee_type
FROM  employee
()
SELECT
  engineer.id  AS  engineer_id,
  employee.id  AS  employee_id,
  employee.type  AS  employee_type,
  engineer.engineer_name  AS  engineer_engineer_name
FROM  employee  JOIN  engineer  ON  employee.id  =  engineer.id
WHERE  employee.id  IN  (?,  ?)  ORDER  BY  employee.id
(1,  2)
SELECT
  manager.id  AS  manager_id,
  employee.id  AS  employee_id,
  employee.type  AS  employee_type,
  manager.manager_name  AS  manager_manager_name
FROM  employee  JOIN  manager  ON  employee.id  =  manager.id
WHERE  employee.id  IN  (?)  ORDER  BY  employee.id
(3,) 

另请参阅

使用 selectin_polymorphic()

#3948

可接收临时 SQL 表达式的 ORM 属性

新的 ORM 属性类型query_expression()被添加,类似于deferred(),不同之处在于其 SQL 表达式在查询时使用新选项with_expression()确定;如果未指定,则属性默认为None

from sqlalchemy.orm import query_expression
from sqlalchemy.orm import with_expression
class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    x = Column(Integer)
    y = Column(Integer)
    # will be None normally...
    expr = query_expression()
# but let's give it x + y
a1 = session.query(A).options(with_expression(A.expr, A.x + A.y)).first()
print(a1.expr)

另请参阅

查询时 SQL 表达式作为映射属性

#3058

ORM 支持多表删除

ORM Query.delete() 方法支持多表条件的 DELETE,就像在支持多表条件的 DELETE 中介绍的那样。该功能与 0.8 中首次引入的 UPDATE 的多表条件相同,详细描述在 Query.update()支持 UPDATE…FROM 中。

下面,我们对SomeEntity执行一个 DELETE 操作,添加一个 FROM 子句(或等效的,取决于后端)对SomeOtherEntity

query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter(
    SomeOtherEntity.foo == "bar"
).delete()

另请参阅

支持多表条件的 DELETE

#959

支持混合属性,复合属性的批量更新

混合属性(例如sqlalchemy.ext.hybrid)以及复合属性(复合列类型)现在都支持在使用Query.update()时用于 UPDATE 语句的 SET 子句中。

对于混合属性,可以直接使用简单表达式,或者可以使用新的装饰器hybrid_property.update_expression()将一个值分解为多个列/表达式:

class Person(Base):
    # ...
    first_name = Column(String(10))
    last_name = Column(String(10))
    @hybrid.hybrid_property
    def name(self):
        return self.first_name + " " + self.last_name
    @name.expression
    def name(cls):
        return func.concat(cls.first_name, " ", cls.last_name)
    @name.update_expression
    def name(cls, value):
        f, l = value.split(" ", 1)
        return [(cls.first_name, f), (cls.last_name, l)]

上面,一个 UPDATE 可以使用以下方式呈现:

session.query(Person).filter(Person.id == 5).update({Person.name: "Dr. No"})

类似的功能也适用于复合类型,其中复合值将被拆分为其各个列以进行批量更新:

session.query(Vertex).update({Edge.start: Point(3, 4)})

另请参阅

允许批量 ORM 更新

混合属性支持在子类之间重用,重新定义 @getter

sqlalchemy.ext.hybrid.hybrid_property 类现在支持在子类之间多次调用修改器,如 @setter@expression 等,并且现在提供了一个 @getter 修改器,以便可以在子类或其他类之间重新用特定的混合属性。这与标准 Python 中 @property 的行为类似:

class FirstNameOnly(Base):
    # ...
    first_name = Column(String)
    @hybrid_property
    def name(self):
        return self.first_name
    @name.setter
    def name(self, value):
        self.first_name = value
class FirstNameLastName(FirstNameOnly):
    # ...
    last_name = Column(String)
    @FirstNameOnly.name.getter
    def name(self):
        return self.first_name + " " + self.last_name
    @name.setter
    def name(self, value):
        self.first_name, self.last_name = value.split(" ", maxsplit=1)
    @name.expression
    def name(cls):
        return func.concat(cls.first_name, " ", cls.last_name)

在上面的例子中,FirstNameOnly.name 混合属性被 FirstNameLastName 子类引用,以便将其专门重新用于新的子类。这是通过在每次调用 @getter@setter 以及所有其他修改器方法(如 @expression)中将混合对象复制到一个新对象中来实现的,从而保持先前混合属性的定义不变。以前,像 @setter 这样的方法会就地修改现有的混合属性,干扰了超类上的定义。

注意

请务必阅读在子类之间重用混合属性的文档,了解如何覆盖hybrid_property.expression()hybrid_property.comparator() 的重要注意事项,因为在某些情况下可能需要一个特殊的限定符 hybrid_property.overrides 来避免与 QueryableAttribute 的名称冲突。

注意

这种对 @hybrid_property 的更改意味着,当向 @hybrid_property 添加 setter 和其他状态时,方法必须保留原始混合属性的名称,否则新的带有额外状态的混合属性将以不匹配的名称存在于类中。这与标准 Python 中 @property 的行为相同:

class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name
    # WRONG - will raise AttributeError: can't set attribute when
    # assigning to .name
    @name.setter
    def _set_name(self, value):
        self.first_name = value
class FirstNameOnly(Base):
    @hybrid_property
    def name(self):
        return self.first_name
    # CORRECT - note regular Python @property works the same way
    @name.setter
    def name(self, value):
        self.first_name = value

#3911

#3912

新的 bulk_replace 事件

为了适应 在批量集合设置之前比较时,@validates 方法接收所有值 中描述的验证用例,添加了一个新的 AttributeEvents.bulk_replace() 方法,它与 AttributeEvents.append()AttributeEvents.remove() 事件一起调用。“bulk_replace” 在 “append” 和 “remove”  之前调用,以便在比较现有集合之前修改集合。之后,单个项目将被附加到新的目标集合,触发针对集合中新项目的 “append”  事件,就像以前的行为一样。下面同时说明了 “bulk_replace” 和 “append”,包括如果使用集合赋值,“append”  将接收到已由 “bulk_replace” 处理过的对象的情况。新的符号 attributes.OP_BULK_REPLACE 可以用于确定此 “append” 事件是否是批量替换的第二部分:

from sqlalchemy.orm.attributes import OP_BULK_REPLACE
@event.listens_for(SomeObject.collection, "bulk_replace")
def process_collection(target, values, initiator):
    values[:] = [_make_value(value) for value in values]
@event.listens_for(SomeObject.collection, "append", retval=True)
def process_collection(target, value, initiator):
    # make sure bulk_replace didn't already do it
    if initiator is None or initiator.op is not OP_BULK_REPLACE:
        return _make_value(value)
    else:
        return value

#3896

新的 sqlalchemy.ext.mutable 的“modified”事件处理器

新的事件处理器 AttributeEvents.modified() 被添加了,它会在调用 flag_modified() 方法时触发,通常这个方法是从 sqlalchemy.ext.mutable 扩展中调用的:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy import event
Base = declarative_base()
class MyDataClass(Base):
    __tablename__ = "my_data"
    id = Column(Integer, primary_key=True)
    data = Column(MutableDict.as_mutable(JSONEncodedDict))
@event.listens_for(MyDataClass.data, "modified")
def modified_json(instance):
    print("json value modified:", instance.data)

上面的事件处理程序将在对 .data 字典进行原位更改时触发。

#3303

添加了“for update”参数到 Session.refresh

Session.refresh() 方法添加了新参数 Session.refresh.with_for_update。当 Query.with_lockmode() 方法被弃用,改用 Query.with_for_update() 后,Session.refresh() 方法从未更新以反映新选项:

session.refresh(some_object, with_for_update=True)

Session.refresh.with_for_update 参数接受一个选项字典,这些选项将作为传递给 Query.with_for_update() 的相同参数:

session.refresh(some_objects, with_for_update={"read": True})

新参数取代了 Session.refresh.lockmode 参数。

#3991

可变集合 MutableSet 和 可变列表 MutableList 支持原地变异操作符

MutableSet 实现了原地变异操作符 __ior____iand____ixor____isub__,以及为 MutableList 实现了 __iadd__。虽然这些方法以前可以成功更新集合,但它们不会正确触发更改事件。这些操作符像以前一样改变集合,但另外会发出正确的更改事件,以便更改成为下一个刷新过程的一部分:

model = session.query(MyModel).first()
model.json_set &= {1, 3}

#3853

AssociationProxyany()has()contains() 方法可以与链式关联代理一起使用

AssociationProxy.any()AssociationProxy.has()AssociationProxy.contains() 比较方法现在支持链接到一个属性,该属性本身也是一个 AssociationProxy,递归地。下面,A.b_values 是一个关联代理,链接到 AtoB.bvalue,而 AtoB.bvalue 本身是一个关联代理,链接到 B

class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    b_values = association_proxy("atob", "b_value")
    c_values = association_proxy("atob", "c_value")
class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    value = Column(String)
    c = relationship("C")
class C(Base):
    __tablename__ = "c"
    id = Column(Integer, primary_key=True)
    b_id = Column(ForeignKey("b.id"))
    value = Column(String)
class AtoB(Base):
    __tablename__ = "atob"
    a_id = Column(ForeignKey("a.id"), primary_key=True)
    b_id = Column(ForeignKey("b.id"), primary_key=True)
    a = relationship("A", backref="atob")
    b = relationship("B", backref="atob")
    b_value = association_proxy("b", "value")
    c_value = association_proxy("b", "c")

我们可以使用 AssociationProxy.contains()A.b_values 上进行查询,以跨越两个代理 A.b_valuesAtoB.b_value

>>> s.query(A).filter(A.b_values.contains("hi")).all()
SELECT  a.id  AS  a_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  atob
WHERE  a.id  =  atob.a_id  AND  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  atob.b_id  AND  b.value  =  :value_1))) 

类似地,我们可以使用 AssociationProxy.any()A.c_values 上进行查询,以跨越两个代理 A.c_valuesAtoB.c_value

>>> s.query(A).filter(A.c_values.any(value="x")).all()
SELECT  a.id  AS  a_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  atob
WHERE  a.id  =  atob.a_id  AND  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  atob.b_id  AND  (EXISTS  (SELECT  1
FROM  c
WHERE  b.id  =  c.b_id  AND  c.value  =  :value_1))))) 

#3769

身份键增强以支持分片

ORM 现在使用的身份键结构包含一个额外成员,因此来自不同上下文的两个相同主键可以共存于同一身份映射中。

水平分片的示例已更新以说明这种行为。示例展示了一个分片类WeatherLocation,它引用一个依赖的WeatherReport对象,其中WeatherReport类映射到一个存储简单整数主键的表。来自不同数据库的两个WeatherReport对象可能具有相同的主键值。现在的示例说明了一个新的identity_token字段跟踪这种差异,以便这两个对象可以共存于同一个标识映射中:

tokyo = WeatherLocation("Asia", "Tokyo")
newyork = WeatherLocation("North America", "New York")
tokyo.reports.append(Report(80.0))
newyork.reports.append(Report(75))
sess = create_session()
sess.add_all([tokyo, newyork, quito])
sess.commit()
# the Report class uses a simple integer primary key.  So across two
# databases, a primary key will be repeated.  The "identity_token" tracks
# in memory that these two identical primary keys are local to different
# databases.
newyork_report = newyork.reports[0]
tokyo_report = tokyo.reports[0]
assert inspect(newyork_report).identity_key == (Report, (1,), "north_america")
assert inspect(tokyo_report).identity_key == (Report, (1,), "asia")
# the token representing the originating shard is also available directly
assert inspect(newyork_report).identity_token == "north_america"
assert inspect(tokyo_report).identity_token == "asia"

#4137


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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十四)(5)
SqlAlchemy 2.0 中文文档(七十四)
41 6
|
3月前
|
SQL JSON 测试技术
SqlAlchemy 2.0 中文文档(七十五)(2)
SqlAlchemy 2.0 中文文档(七十五)
38 3
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(七十四)(3)
SqlAlchemy 2.0 中文文档(七十四)
40 1
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(七十五)(4)
SqlAlchemy 2.0 中文文档(七十五)
45 1
|
3月前
|
SQL Python
SqlAlchemy 2.0 中文文档(七十四)(4)
SqlAlchemy 2.0 中文文档(七十四)
27 6
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(七十五)(1)
SqlAlchemy 2.0 中文文档(七十五)
62 4
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十四)(1)
SqlAlchemy 2.0 中文文档(七十四)
49 1
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(2)
SqlAlchemy 2.0 中文文档(七十六)
62 2
|
3月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(1)
SqlAlchemy 2.0 中文文档(七十六)
35 2
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(七十五)(3)
SqlAlchemy 2.0 中文文档(七十五)
26 0