SqlAlchemy 2.0 中文文档(五十七)(1)

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


原文:docs.sqlalchemy.org/en/20/contents.html

SQLAlchemy 2.0 有哪些新功能?

原文:docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html

读者注意事项

SQLAlchemy 2.0 的过渡文档分为 两个 文档 - 一个详细说明了从 1.x 到 2.x 系列的主要 API 转换,另一个详细说明了与 SQLAlchemy 1.4 相关的新功能和行为:

  • SQLAlchemy 2.0 - Major Migration Guide - 1.x 到 2.x API 转换
  • SQLAlchemy 2.0 有哪些新功能? - 本文档,SQLAlchemy 2.0 的新功能和行为

尚未将其 1.4 应用程序更新为遵循 SQLAlchemy 2.0 引擎和 ORM 约定的读者可以导航到 SQLAlchemy 2.0 - Major Migration Guide 了解确保 SQLAlchemy 2.0 兼容性的指南,这是在版本 2.0 下拥有可工作代码的先决条件。

关于本文档

本文描述了 SQLAlchemy 版本 1.4 与版本 2.0 之间的变化, 1.x 风格和 2.0 风格的主要变化无关。读者应该从 SQLAlchemy 2.0 - Major Migration Guide 文档开始,以了解 1.x 和 2.x 系列之间的主要兼容性变化的整体图片。

除了主要的 1.x->2.x 迁移路径之外,SQLAlchemy 2.0 中下一个最大的范式转变是与PEP 484类型实践和当前能力的深度集成,特别是在 ORM 中。受 Python dataclasses启发的新型基于类型的 ORM 声明风格,以及与 dataclasses 本身的新集成,补充了一种不再需要存根并且在从 SQL 语句到结果集的类型感知方法链方面取得了很大进展的整体方法。

Python 类型的突出地位不仅仅在于使得诸如mypy之类的类型检查器可以无需插件而运行;更重要的是,它使得像vscodepycharm这样的集成开发环境能够在辅助编写 SQLAlchemy 应用程序时发挥更加积极的作用。

Core 和 ORM 中的新类型支持 - 不再使用存根 / 扩展

与版本 1.4 中通过sqlalchemy2-stubs包提供的临时方法相比,Core 和 ORM 的类型化方法已经完全重新设计。新方法从 SQLAlchemy 中最基本的元素开始,即Column,或者更准确地说是支撑所有具有类型的 SQL 表达式的ColumnElement。然后,这种表达级别的类型化扩展到语句构造、语句执行和结果集,并最终扩展到 ORM,其中新的 declarative 形式允许完全类型化的 ORM 模型,从语句到结果集完全集成。

提示

对于 2.0 系列,类型化支持应该被视为beta 级别软件。类型化细节可能会更改,但不计划进行重大的不兼容性更改。

SQL 表达式/语句/结果集类型化

本节提供了关于 SQLAlchemy 新的 SQL 表达式类型化方法的背景和示例,该方法从基本的ColumnElement构造扩展到 SQL 语句和结果集,以及 ORM 映射的领域。

理论基础和概述

提示

本节是一个架构讨论。跳转到 SQL Expression Typing - Examples 只需查看新类型化的外观。

sqlalchemy2-stubs中,SQL 表达式被标记为泛型,然后引用一个TypeEngine对象,比如IntegerDateTimeString作为它们的泛型参数(例如Column[Integer])。这本身就是与原始的 Dropbox sqlalchemy-stubs包不同的地方,原始的包直接将Column及其基本构造标记为 Python 类型的泛型,比如intdatetimestr。人们希望由于Integer / DateTime / String本身与int / datetime / str泛型相关,会有方法来保持两个级别的信息,并能够通过中间构造TypeEngine从列表达式中提取 Python 类型。然而,事实并非如此,因为PEP 484实际上没有足够丰富的功能集来使这成为可行的选择,缺乏诸如higher kinded TypeVars之类的功能。

因此,在对PEP 484当前功能进行深入评估之后,SQLAlchemy 2.0 认识到了在这个领域原始的sqlalchemy-stubs的智慧,并回归到了直接将列表达式链接到 Python 类型的做法。这意味着,如果有 SQL 表达式到不同子类型的情况,比如Column(VARCHAR)Column(Unicode),这两种String子类型的具体细节不会被传递,因为类型只会传递str,但在实践中,这通常不是一个问题,直接出现 Python 类型通常更有用,因为它代表了将要直接存储和接收的 Python 数据。

具体来说,这意味着像 Column('id', Integer) 这样的表达式被类型为 Column[int]。这允许建立一个可行的 SQLAlchemy 构造 -> Python 数据类型的流水线,而无需使用类型插件。关键是,它允许完全与 ORM 的范式进行互操作,即使用引用 ORM 映射类类型的 select()Row 构造(例如,包含用户映射实例的 Row,例如在我们的教程中使用的 UserAddress 示例)。虽然 Python 的类型当前对于元组类型的定制支持非常有限(其中 PEP 646,第一个试图处理类似元组的对象的 pep,在功能上受到故意限制,并且本身尚不适用于任意元组操作),但已经设计了一个相当不错的方法,允许基本的 select() -> Result -> Row 类型功能,包括用于 ORM 类的功能,在要将 Row 对象展开为单独的列条目时,会添加一个小的面向类型的访问器,允许各个 Python 值保持与它们来源于的 SQL 表达式相关联的 Python 类型(翻译:它可以工作)。

SQL 表达式类型 - 示例

类型行为简要介绍。评论指示在 vscode 中悬停在代码上会看到什么(或者在使用 reveal_type() 助手时,大致会显示什么类型工具):

  • 分配给 SQL 表达式的简单 Python 类型
# (variable) str_col: ColumnClause[str]
str_col = column("a", String)
# (variable) int_col: ColumnClause[int]
int_col = column("a", Integer)
# (variable) expr1: ColumnElement[str]
expr1 = str_col + "x"
# (variable) expr2: ColumnElement[int]
expr2 = int_col + 10
# (variable) expr3: ColumnElement[bool]
expr3 = int_col == 15
  • 分配给 select() 构造的单个 SQL 表达式,以及任何返回行的构造,包括返回行的 DML,例如带有 InsertInsert.returning(),都被打包成一个 Tuple[] 类型,其中保留了每个元素的 Python 类型。
# (variable) stmt: Select[Tuple[str, int]]
stmt = select(str_col, int_col)
# (variable) stmt: ReturningInsert[Tuple[str, int]]
ins_stmt = insert(table("t")).returning(str_col, int_col)
  • 从任何返回行构造的Tuple[]类型,在调用.execute()方法时,传递到ResultRow。为了将Row对象解包为元组,Row.tuple()Row.t访问器基本上将Row强制转换为相应的Tuple[](尽管在运行时仍然是相同的Row对象)。
with engine.connect() as conn:
    # (variable) stmt: Select[Tuple[str, int]]
    stmt = select(str_col, int_col)
    # (variable) result: Result[Tuple[str, int]]
    result = conn.execute(stmt)
    # (variable) row: Row[Tuple[str, int]] | None
    row = result.first()
    if row is not None:
        # for typed tuple unpacking or indexed access,
        # use row.tuple() or row.t  (this is the small typing-oriented accessor)
        strval, intval = row.t
        # (variable) strval: str
        strval
        # (variable) intval: int
        intval
  • 对于单列语句的标量值,像Connection.scalar()Result.scalars()等方法会做正确的事情。
# (variable) data: Sequence[str]
data = connection.execute(select(str_col)).scalars().all()
  • 上述对于返回行构造的支持与 ORM 映射类一起运作最好,因为映射类可以为其成员列出具体类型。下面的示例设置了一个类,使用了新的类型感知语法,在下一节中描述:
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    addresses: Mapped[List["Address"]] = relationship()
class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(primary_key=True)
    email_address: Mapped[str]
    user_id = mapped_column(ForeignKey("user_account.id"))
  • 使用上述映射,属性从语句到结果集都有类型并自我表达:
with Session(engine) as session:
    # (variable) stmt: Select[Tuple[int, str]]
    stmt_1 = select(User.id, User.name)
    # (variable) result_1: Result[Tuple[int, str]]
    result_1 = session.execute(stmt_1)
    # (variable) intval: int
    # (variable) strval: str
    intval, strval = result_1.one().t
  • 映射类本身也是类型,并且表现得相同,例如针对两个映射类进行的 SELECT:
with Session(engine) as session:
    # (variable) stmt: Select[Tuple[User, Address]]
    stmt_2 = select(User, Address).join_from(User, Address)
    # (variable) result_2: Result[Tuple[User, Address]]
    result_2 = session.execute(stmt_2)
    # (variable) user_obj: User
    # (variable) address_obj: Address
    user_obj, address_obj = result_2.one().t
  • 在选择映射类时,像aliased这样的构造也可以工作,保持原始映射类的列级属性以及语句预期的返回类型:
with Session(engine) as session:
    # this is in fact an Annotated type, but typing tools don't
    # generally display this
    # (variable) u1: Type[User]
    u1 = aliased(User)
    # (variable) stmt: Select[Tuple[User, User, str]]
    stmt = select(User, u1, User.name).filter(User.id == 5)
    # (variable) result: Result[Tuple[User, User, str]]
    result = session.execute(stmt)
  • Core Table 还没有一个良好的方法来在通过Table.c访问它们时维护Column对象的类型。
    由于Table设置为类的一个实例,而Table.c访问器通常通过名称动态访问Column对象,因此尚未建立针对此的已知类型方法;需要一些替代语法。
  • ORM 类、标量等都很好用。
    典型的 ORM 类选择用例,作为标量或元组,所有工作都可以,无论是 2.0 还是 1.x 风格的查询,都可以得到精确的类型,要么是自身,要么包含在适当的容器内,如 Sequence[]List[]Iterator[]
# (variable) users1: Sequence[User]
users1 = session.scalars(select(User)).all()
# (variable) user: User
user = session.query(User).one()
# (variable) user_iter: Iterator[User]
user_iter = iter(session.scalars(select(User)))
  • 旧版 Query 也获得了元组类型。
    对于 Query 的类型支持远远超出了 sqlalchemy-stubssqlalchemy2-stubs 提供的范围,其中标量对象和元组类型的 Query 对象在大多数情况下都会保留结果级别的类型:
# (variable) q1: RowReturningQuery[Tuple[int, str]]
q1 = session.query(User.id, User.name)
# (variable) rows: List[Row[Tuple[int, str]]]
rows = q1.all()
# (variable) q2: Query[User]
q2 = session.query(User)
# (variable) users: List[User]
users = q2.all()
陷阱 - 所有存根必须被卸载

类型支持的一个关键注意事项是,所有 SQLAlchemy 存根包都必须被卸载 才能使类型化工作。当针对 Python 虚拟环境运行 mypy 时,只需卸载这些包即可。但是,目前 typeshed 中也包含 SQLAlchemy 存根包,typeshed 本身被捆绑到一些类型工具中,如 Pylance,因此在某些情况下,可能需要定位这些包的文件并删除它们,以确保新的类型化能够正确工作。

一旦 SQLAlchemy 2.0 发布为最终状态,typeshed 将从其自己的存根源中删除 SQLAlchemy。

ORM 声明性模型

SQLAlchemy 1.4 引入了第一个使用 sqlalchemy2-stubs 和 Mypy Plugin 的 SQLAlchemy 本机 ORM 类型支持的方法。在 SQLAlchemy 2.0 中,Mypy 插件 仍然可用,并已更新以与 SQLAlchemy 2.0 的类型系统一起工作。但是,现在应该将其视为已弃用,因为应用程序现在有了采用新的不使用插件或存根的类型支持的简单路径。

概览

新系统的基本方法是,当使用完全声明性模型(即不使用混合声明性或命令式配置,这些配置保持不变)时,映射列声明首先通过检查每个属性声明左侧的类型注释(如果存在)在运行时派生。左手类型注释应包含在Mapped泛型类型中,否则不认为属性是映射属性。然后,属性声明可以引用右手边的mapped_column()构造,该构造用于提供有关要生成和映射的Column的附加 Core 级架构信息。如果左侧存在Mapped注释,则此右侧声明是可选的;如果左侧没有注释,则mapped_column()可用作Column指令的精确替代,在这种情况下,它将提供更准确(但不精确)的属性类型行为,即使没有注释也是如此。

这种方法的灵感来自于 Python 的 dataclasses 方法,该方法从左侧开始注释,然后允许在右侧进行可选的 dataclasses.field() 规范;与 dataclasses 方法的关键区别在于 SQLAlchemy 的方法严格地 选择加入,其中使用 Column 而没有任何类型注释的现有映射继续按照其原来的方式工作,而且 mapped_column() 构造可以直接替代 Column 而不需要任何显式类型注释。只有在需要精确的属性级 Python 类型时才需要使用 Mapped 进行显式注释。这些注释可以根据需要在每个属性上使用,对于那些具体类型有帮助的属性;使用 mapped_column() 的非注释属性将在实例级别被标记为 Any

迁移现有映射

迁移到新的 ORM 方法开始时更加冗长,但随着可用的新功能的充分使用,变得比以前更加简洁。以下步骤详细说明了一个典型的过渡,然后继续说明了一些更多的选项。

第一步 - declarative_base()DeclarativeBase 取代。

在 Python 类型中观察到的一个限制是似乎没有能力从函数动态生成一个类,然后这个类被理解为新类的基础。为了解决这个问题而不使用插件,通常对 declarative_base() 的调用可以被替换为使用 DeclarativeBase 类,该类产生与通常相同的 Base 对象,但是类型工具理解它:

from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
    pass
第二步 - 用 mapped_column() 替换 Declarative 中的 Column

mapped_column()是一个 ORM 类型感知的构造,可以直接替换为Column的使用。给定一个 1.x 风格的映射如下:

from sqlalchemy import Column
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user_account"
    id = Column(Integer, primary_key=True)
    name = Column(String(30), nullable=False)
    fullname = Column(String)
    addresses = relationship("Address", back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    email_address = Column(String, nullable=False)
    user_id = Column(ForeignKey("user_account.id"), nullable=False)
    user = relationship("User", back_populates="addresses")

我们用mapped_column()替换Column; 不需要更改任何参数:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user_account"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(30), nullable=False)
    fullname = mapped_column(String)
    addresses = relationship("Address", back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    email_address = mapped_column(String, nullable=False)
    user_id = mapped_column(ForeignKey("user_account.id"), nullable=False)
    user = relationship("User", back_populates="addresses")

上面的单独列 尚未使用 Python 类型进行类型化,而是被类型化为Mapped[Any];这是因为我们可以声明任何列为Optional或者不声明,而且没有办法在我们明确地对其进行类型化时有一个“猜测”。

但是,在这一步,我们上面的映射已经为所有属性设置了适当的描述符类型,并且可以用于查询以及实例级别的操作,所有这些操作都将以不使用插件的 mypy –strict 模式通过。

第三步 - 使用Mapped根据需要应用确切的 Python 类型。

这可以用于所有需要确切类型的属性;那些可以留作Any的属性可以跳过。为了上下文,我们还说明了在一个relationship()中应用确切类型时如何使用Mapped。这个中间步骤中的映射会更冗长,但是熟练之后,这一步可以与后续步骤结合起来更直接地更新映射:

from typing import List
from typing import Optional
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(30), nullable=False)
    fullname: Mapped[Optional[str]] = mapped_column(String)
    addresses: Mapped[List["Address"]] = relationship("Address", back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    email_address: Mapped[str] = mapped_column(String, nullable=False)
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"), nullable=False)
    user: Mapped["User"] = relationship("User", back_populates="addresses")

在这一点上,我们的 ORM 映射是完全类型化的,并将生成精确类型的select()QueryResult构造。我们现在可以继续简化映射声明中的冗余部分。

第四步 - 删除不再需要的mapped_column()指令

所有的nullable参数都可以使用Optional[]隐含;在没有Optional[]的情况下,nullable默认为False。所有没有参数的 SQL 类型,如IntegerString,可以单独表示为 Python 注释。不带参数的mapped_column()指令可以完全移除。relationship()现在从左侧注释派生其类,还支持前向引用(正如relationship()已经支持了十年的字符串型前向引用一样 😉 ):

from typing import List
from typing import Optional
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user_account"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    fullname: Mapped[Optional[str]]
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id: Mapped[int] = mapped_column(primary_key=True)
    email_address: Mapped[str]
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
    user: Mapped["User"] = relationship(back_populates="addresses")
第五步 - 利用 pep-593 Annotated 将常见指令打包到类型中

这是一项根本性的新功能,提供了一种替代或补充方法,用于声明式混合作为提供类型定向配置的手段,并且在大多数情况下也替代了declared_attr装饰函数的需要。

首先,声明式映射允许将 Python 类型映射到 SQL 类型,例如将str映射到String,通过使用registry.type_annotation_map进行自定义。使用PEP 593 Annotated 允许我们创建特定 Python 类型的变体,以便可以使用相同的类型,例如str,每个类型提供String的变体,如下所示,其中使用名为str50Annotated str 将指示String(50)

from typing_extensions import Annotated
from sqlalchemy.orm import DeclarativeBase
str50 = Annotated[str, 50]
# declarative base with a type-level override, using a type that is
# expected to be used in multiple places
class Base(DeclarativeBase):
    type_annotation_map = {
        str50: String(50),
    }

其次,如果使用Annotated[],声明式将从左侧类型中提取完整的mapped_column()定义,方法是将mapped_column()结构作为Annotated[]结构的任何参数传递(感谢@adriangb01说明这个想法)。在未来的版本中,这项功能可能会扩展到包括relationship()composite()和其他结构,但目前仅限于mapped_column()。下面的示例除了我们的str50示例外,还添加了额外的Annotated类型,以说明此功能:

from typing_extensions import Annotated
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
# declarative base from previous example
str50 = Annotated[str, 50]
class Base(DeclarativeBase):
    type_annotation_map = {
        str50: String(50),
    }
# set up mapped_column() overrides, using whole column styles that are
# expected to be used in multiple places
intpk = Annotated[int, mapped_column(primary_key=True)]
user_fk = Annotated[int, mapped_column(ForeignKey("user_account.id"))]
class User(Base):
    __tablename__ = "user_account"
    id: Mapped[intpk]
    name: Mapped[str50]
    fullname: Mapped[Optional[str]]
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id: Mapped[intpk]
    email_address: Mapped[str50]
    user_id: Mapped[user_fk]
    user: Mapped["User"] = relationship(back_populates="addresses")

上面,与Mapped[str50]Mapped[intpk]Mapped[user_fk]映射的列从registry.type_annotation_map以及直接使用Annotated构造从中提取,以便重用预先建立的类型和列配置。

可选步骤 - 将映射类转换为dataclasses

我们可以将映射类转换为dataclasses,其中一个关键优势是我们可以构建一个严格类型化的__init__()方法,具有明确的位置参数、关键字参数和默认参数,更不用说我们可以免费获得__str__()__repr__()等方法。接下来的部分作为 ORM 模型映射的数据类的本机支持进一步说明了以上模型的转换。

从第 3 步开始支持类型标注

通过上述示例,从“第 3 步”开始的任何示例都将包括模型的属性已进行类型标注,并将填充到select()QueryRow对象中:

# (variable) stmt: Select[Tuple[int, str]]
stmt = select(User.id, User.name)
with Session(e) as sess:
    for row in sess.execute(stmt):
        # (variable) row: Row[Tuple[int, str]]
        print(row)
    # (variable) users: Sequence[User]
    users = sess.scalars(select(User)).all()
    # (variable) users_legacy: List[User]
    users_legacy = sess.query(User).all()

另请参阅

具有 mapped_column()的声明性表 - 更新的声明性文档,用于声明性生成和映射Table列。 ### 使用传统的 Mypy 类型化模型

使用 Mypy 插件的 SQLAlchemy 应用程序,其中明确注释不使用Mapped在其注释中的,当使用诸如relationship()之类的构造时,将根据新系统标记为错误。

部分迁移到 2.0 步骤六 - 为显式类型的 ORM 模型添加 allow_unmapped 说明了如何为使用显式注释的遗留 ORM 模型临时禁用这些错误的触发。

另请参阅

迁移到 2.0 步骤六 - 为显式类型的 ORM 模型添加 allow_unmapped ### 作为 ORM 模型映射的数据类的本机支持

在上面介绍的新 ORM 声明式特性中,引入了新的mapped_column()构造,以及可选使用PEP 593 Annotated进行类型中心化映射的示例。我们可以通过将其与 Python 的dataclasses集成,进一步完善这种映射。这一新特性是通过PEP 681实现的,允许类型检查器识别与dataclass兼容的类,或者完全是dataclass,但是通过其他 API 声明的类。

使用dataclasses特性,映射类获得了一个__init__()方法,支持位置参数以及可选关键字参数的自定义默认值。如前所述,dataclasses还会生成许多有用的方法,比如__str__()__eq__()dataclasses的序列化方法,比如dataclasses.asdict()dataclasses.astuple()也可以使用,但目前不支持自引用结构,这使得它们对于具有双向关系的映射来说不太适用。

SQLAlchemy 当前的集成方法将用户定义的类转换为一个真实的 dataclass,以提供运行时功能;该特性利用了  SQLAlchemy 1.4 中引入的现有 dataclass 特性,在 Python Dataclasses, attrs  Supported w/ Declarative, Imperative Mappings  中生成一个等效的运行时映射,具有完全集成的配置样式,比以前的方法更正确地类型化。

为了支持符合 PEP 681 的数据类,ORM 构造如 mapped_column()relationship() 现在接受额外的 PEP 681 参数 initdefaultdefault_factory,这些参数将传递到数据类创建过程中。这些参数目前必须以右侧的显式指令的形式存在,就像它们在 dataclasses.field() 中使用时一样;它们目前不能局部存在于左侧的 Annotated 构造中。为了支持方便地使用 Annotated 并仍然支持数据类配置,mapped_column() 可以将一组最小的右侧参数与位于左侧的 Annotated 构造内的现有 mapped_column() 构造合并,以便保持大部分简洁性,如下所示。

为了启用使用类继承的数据类,我们利用了 MappedAsDataclass mixin,可以直接在每个类上使用,也可以在 Base 类上使用,如下所示,在这里我们进一步修改了来自 “Step 5” 的示例映射 ORM 声明性模型。

from typing_extensions import Annotated
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import MappedAsDataclass
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(MappedAsDataclass, DeclarativeBase):
  """subclasses will be converted to dataclasses"""
intpk = Annotated[int, mapped_column(primary_key=True)]
str30 = Annotated[str, mapped_column(String(30))]
user_fk = Annotated[int, mapped_column(ForeignKey("user_account.id"))]
class User(Base):
    __tablename__ = "user_account"
    id: Mapped[intpk] = mapped_column(init=False)
    name: Mapped[str30]
    fullname: Mapped[Optional[str]] = mapped_column(default=None)
    addresses: Mapped[List["Address"]] = relationship(
        back_populates="user", default_factory=list
    )
class Address(Base):
    __tablename__ = "address"
    id: Mapped[intpk] = mapped_column(init=False)
    email_address: Mapped[str]
    user_id: Mapped[user_fk] = mapped_column(init=False)
    user: Mapped["User"] = relationship(back_populates="addresses", default=None)

上述映射已经直接在每个映射类上使用 @dataclasses.dataclass 装饰器设置,同时在设置声明性映射时内部设置了每个 dataclasses.field() 指令,如所示。可以使用按位置配置的 User / Address 结构创建:

>>> u1 = User("username", fullname="full name", addresses=[Address("email@address")])
>>> u1
User(id=None, name='username', fullname='full name', addresses=[Address(id=None, email_address='email@address', user_id=None, user=...)])

另请参阅

声明式数据类映射 ## 除了 MySQL 外,所有后端现在都实现了优化的 ORM 批量插入

在 1.4 系列中引入的戏剧性性能改进,并在 ORM Batch inserts with psycopg2 now batch  statements with RETURNING in most cases 中描述,现已推广到所有支持 RETURNING 的包含后端,除了  MySQL 之外的所有后端:SQLite,MariaDB,PostgreSQL(所有驱动程序)和 Oracle; SQL Server  具有支持,但在版本 2.0.9 中暂时禁用[1]。虽然原始功能对于 psycopg2 驱动程序最为关键,否则在使用cursor.executemany()时会有严重的性能问题,但该变更对于其他  PostgreSQL 驱动程序如 asyncpg 同样关键,因为在使用 RETURNING 时,单语句 INSERT  语句仍然不可接受地慢,以及在使用 SQL Server 时,似乎无论是否使用 RETURNING,INSERT 语句的 executemany  速度都非常慢。

新功能的性能几乎在各个方面都提供了一个数量级的性能增加,当 INSERT ORM 对象时,这些对象没有预先分配的主键值,在下表中有所指示,大多数情况下特定于使用 RETURNING,而这通常不支持 executemany()。

psycopg2 的“快速执行助手”方法包括将一个带有单个参数集的 INSERT…RETURNING  语句转换为一个语句,该语句插入了许多参数集,使用多个“VALUES…”子句,以便一次容纳许多参数集。然后,参数集通常被分批为 1000  个或类似的组,以便没有单个 INSERT 语句过大,并且 INSERT  语句然后为每批参数调用,而不是为每个单独的参数集调用。主键值和服务器默认值由 RETURNING 返回,这仍然有效,因为每个语句执行都是使用cursor.execute()调用的,而不是cursor.executemany()

这允许在一个语句中插入许多行,同时还能返回新生成的主键值以及 SQL 和服务器默认值。 SQLAlchemy 在历史上一直需要为每个参数集调用一个语句,因为它依赖于 Python DBAPI 功能,如cursor.lastrowid,这些功能不支持多行。

由于大多数数据库现在都提供 RETURNING(明显的例外是 MySQL,鉴于 MariaDB 支持它),新的更改将 psycopg2  的“快速执行助手”方法推广到所有支持 RETURNING 的方言,现在包括 SQlite 和 MariaDB,对于支持“executemany 加  RETURNING”的其他方法不可能的方言,包括 SQLite、MariaDB 和所有 PG 驱动程序。用于 Oracle 的  cx_Oracle 和 oracledb 驱动程序本地支持使用 executemany 返回,这也已经实现以提供等效的性能改进。由于  SQLite 和 MariaDB 现在支持 RETURNING,ORM 对cursor.lastrowid的使用几乎已经成为过去,只有 MySQL 仍然依赖它。

对于不使用 RETURNING 的 INSERT 语句,大多数后端使用传统的 executemany()行为,当前的例外是 psycopg2,它的 executemany()性能总体上非常慢,并且仍然通过“insertmanyvalues”方法得到改进。


SqlAlchemy 2.0 中文文档(五十七)(2)https://developer.aliyun.com/article/1563164

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