SqlAlchemy 2.0 中文文档(四)

2024-06-26 14:23:32 浏览数 (1)

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

进一步阅读

原文:docs.sqlalchemy.org/en/20/tutorial/further_reading.html

下面的章节是讨论本教程中概念的主要顶级章节,更详细地描述了每个子系统的许多其他特性。

核心基础参考

  • 与引擎和连接工作
  • 模式定义语言
  • SQL 语句和表达式 API
  • SQL 数据类型对象

ORM 基础参考

  • ORM 映射类配置
  • 关系配置
  • 使用会话
  • ORM 查询指南

SQLAlchemy ORM

原文:docs.sqlalchemy.org/en/20/orm/index.html

在这里,介绍并完整描述了对象关系映射器。如果你想要使用为你自动构建的更高级别的 SQL,并且自动持久化 Python 对象,请首先转到教程。

  • ORM 快速入门
    • 声明模型
    • 创建一个引擎
    • 发出 CREATE TABLE DDL
    • 创建对象并持久化
    • 简单 SELECT
    • 带 JOIN 的 SELECT
    • 进行更改
    • 一些删除操作
    • 深入学习上述概念
  • ORM 映射类配置
    • ORM 映射类概述
    • 使用声明式映射类
    • 与 dataclasses 和 attrs 的集成
    • 将 SQL 表达式作为映射属性
    • 更改属性行为
    • 复合列类型
    • 映射类继承层次结构
    • 非传统映射
    • 配置版本计数器
    • 类映射 API
  • 关系配置
    • 基本关系模式
    • 邻接列表关系
    • 配置关系连接的方式
    • 处理大型集合
    • 集合定制和 API 详细信息
    • 特殊关系持久化模式
    • 使用传统的 ‘backref’ 关系参数
    • 关系 API
  • ORM 查询指南
    • 为 ORM 映射类编写 SELECT 语句
    • 编写继承映射的 SELECT 语句
    • 启用 ORM 的 INSERT、UPDATE 和 DELETE 语句
    • 列加载选项
    • 关系加载技术
    • 用于查询的 ORM API 功能
    • 遗留查询 API
  • 使用会话
    • 会话基础知识
    • 状态管理
    • 级联操作
    • 事务和连接管理
    • 附加持久化技术
    • 上下文/线程本地会话
    • 使用事件跟踪查询、对象和会话的更改
    • 会话 API
  • 事件和内部原理
    • ORM 事件
    • ORM 内部
    • ORM 异常
  • ORM 扩展
    • 异步 I/O(asyncio)
    • 关联代理
    • 自动映射
    • 烘焙查询
    • 声明式扩展
    • Mypy / Pep-484 支持 ORM 映射
    • 变异跟踪
    • 排序列表
    • 水平分片
    • 混合属性
    • 可索引
    • 替代类仪器
  • ORM 示例
    • 映射示例
    • 继承映射示例
    • 特殊 API
    • 扩展 ORM

ORM 快速入门

原文:docs.sqlalchemy.org/en/20/orm/quickstart.html

对于想要快速了解基本 ORM 使用情况的新用户,这里提供了 SQLAlchemy 统一教程中使用的映射和示例的缩写形式。这里的代码可以从干净的命令行完全运行。

由于本节中的描述故意非常简短,请继续阅读完整的 SQLAlchemy 统一教程以获得对这里所说明的每个概念更深入的描述。

从 2.0 版本开始更改:ORM 快速入门已更新为最新的PEP 484兼容功能,使用包括mapped_column()在内的新构造。有关迁移信息,请参见 ORM 声明模型部分。

声明模型

在这里,我们定义模块级别的构造,这些构造将形成我们将从数据库查询的结构。这个结构被称为声明式映射,它一次定义了 Python 对象模型,以及描述存在或将存在于特定数据库中的真实 SQL 表的数据库元数据:

代码语言:javascript复制
>>> 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

>>> 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", cascade="all, delete-orphan"
...     )
...
...     def __repr__(self) -> str:
...         return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

>>> 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")
...
...     def __repr__(self) -> str:
...         return f"Address(id={self.id!r}, email_address={self.email_address!r})"

映射始于一个基类,这个基类上面称为Base,并且是通过对DeclarativeBase类进行简单子类化来创建的。

然后通过对Base进行子类化来创建单独的映射类。一个映射类通常指的是单个特定的数据库表,其名称通过使用__tablename__类级别属性指示。

接下来,通过添加包含称为Mapped的特殊类型注释的属性来声明表的一部分列。每个属性的名称对应于要成为数据库表的一部分的列。每个列的数据类型首先从与每个Mapped注释相关联的 Python 数据类型中获取;int用于INTEGERstr用于VARCHAR,等等。空值性取决于是否使用了Optional[]类型修饰符。可以使用右侧mapped_column()指令中的 SQLAlchemy 类型对象指示更具体的类型信息,例如上面在User.name列中使用的String数据类型。可以使用类型注释映射来自定义 Python 类型和 SQL 类型之间的关联。

mapped_column()指令用于所有需要更具体定制的基于列的属性。除了类型信息外,此指令还接受各种参数,指示有关数据库列的特定详细信息,包括服务器默认值和约束信息,例如在主键和外键中的成员资格。mapped_column()指令接受的参数是 SQLAlchemy Column类所接受的参数的一个超集,该类由 SQLAlchemy 核心用于表示数据库列。

所有 ORM 映射类都要求至少声明一个列作为主键的一部分,通常是通过在那些应该成为主键的mapped_column()对象上使用Column.primary_key参数来实现的。在上面的示例中,User.idAddress.id列被标记为主键。

综合考虑,字符串表名称以及列声明列表的组合在 SQLAlchemy 中被称为 table metadata。在 SQLAlchemy 统一教程的处理数据库元数据中介绍了如何使用核心和 ORM 方法设置表元数据。上述映射是所谓的注释声明表配置的示例。

Mapped 的其他变体可用,最常见的是上面指示的 relationship() 构造。与基于列的属性相比,relationship() 表示两个 ORM 类之间的关联。在上面的示例中,User.addressesUserAddress 连接起来,Address.userAddressUser 连接起来。relationship() 构造介绍于 SQLAlchemy 统一教程 的 处理 ORM 相关对象 部分。

最后,上面的示例类包括一个 __repr__() 方法,这并非必需,但对调试很有用。映射类可以使用诸如 __repr__() 之类的方法自动生成,使用数据类。有关数据类映射的更多信息,请参阅 声明式数据类映射。

创建一个引擎

Engine 是一个工厂,可以为我们创建新的数据库连接,还在 连接池 中保存连接以便快速重用。出于学习目的,我们通常使用一个 SQLite 内存数据库方便起见:

代码语言:javascript复制
>>> from sqlalchemy import create_engine
>>> engine = create_engine("sqlite://", echo=True)

小贴士

echo=True 参数表示连接发出的 SQL 将被记录到标准输出。

Engine 的完整介绍从 建立连接 - 引擎 开始。

发出 CREATE TABLE DDL

利用我们的表格元数据和引擎,我们可以一次性在目标 SQLite 数据库中生成我们的模式,使用的方法是 MetaData.create_all()

代码语言:javascript复制
>>> Base.metadata.create_all(engine)
BEGIN  (implicit)
PRAGMA  main.table_...info("user_account")
...
PRAGMA  main.table_...info("address")
...
CREATE  TABLE  user_account  (
  id  INTEGER  NOT  NULL,
  name  VARCHAR(30)  NOT  NULL,
  fullname  VARCHAR,
  PRIMARY  KEY  (id)
)
...
CREATE  TABLE  address  (
  id  INTEGER  NOT  NULL,
  email_address  VARCHAR  NOT  NULL,
  user_id  INTEGER  NOT  NULL,
  PRIMARY  KEY  (id),
  FOREIGN  KEY(user_id)  REFERENCES  user_account  (id)
)
...
COMMIT 

我们刚刚写的那小段 Python 代码发生了很多事情。要完整了解表格元数据的情况,请在教程中继续阅读 处理数据库元数据 部分。

创建对象并持久化

现在我们已经准备好向数据库插入数据了。我们通过创建UserAddress类的实例来实现这一目标,这些类已经通过声明性映射过程自动建立了__init__()方法。然后,我们使用一个名为 Session 的对象将它们传递给数据库,该对象利用Engine与数据库进行交互。这里使用了Session.add_all()方法一次添加多个对象,并且Session.commit()方法将被用来提交数据库中的任何挂起更改,然后提交当前的数据库事务,无论何时使用Session时,该事务始终处于进行中:

代码语言:javascript复制
>>> from sqlalchemy.orm import Session

>>> with Session(engine) as session:
...     spongebob = User(
...         name="spongebob",
...         fullname="Spongebob Squarepants",
...         addresses=[Address(email_address="spongebob@sqlalchemy.org")],
...     )
...     sandy = User(
...         name="sandy",
...         fullname="Sandy Cheeks",
...         addresses=[
...             Address(email_address="sandy@sqlalchemy.org"),
...             Address(email_address="sandy@squirrelpower.org"),
...         ],
...     )
...     patrick = User(name="patrick", fullname="Patrick Star")
...
...     session.add_all([spongebob, sandy, patrick])
...
...     session.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('spongebob',  'Spongebob Squarepants')
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy',  'Sandy Cheeks')
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('patrick',  'Patrick Star')
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('spongebob@sqlalchemy.org',  1)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy@sqlalchemy.org',  2)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy@squirrelpower.org',  2)
COMMIT 

提示

建议以上述上下文管理器风格使用Session,即使用 Python 的 with: 语句。Session 对象代表了活动的数据库资源,因此确保在完成一系列操作时将其关闭是很好的。在下一节中,我们将保持一个Session仅用于说明目的。

关于创建Session的基础知识请参见使用 ORM Session 执行,更多内容请查看使用 Session 的基础知识。

然后,在使用 ORM 工作单元模式插入行中介绍了一些基本持久性操作的变体。

简单的 SELECT

在数据库中有一些行之后,这是发出 SELECT 语句以加载一些对象的最简单形式。要创建 SELECT 语句,我们使用 select() 函数创建一个新的 Select 对象,然后使用一个 Session 调用它。在查询 ORM 对象时经常有用的方法是 Session.scalars() 方法,它将返回一个 ScalarResult 对象,该对象将遍历我们已选择的 ORM 对象:

代码语言:javascript复制
>>> from sqlalchemy import select

>>> session = Session(engine)

>>> stmt = select(User).where(User.name.in_(["spongebob", "sandy"]))

>>> for user in session.scalars(stmt):
...     print(user)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  IN  (?,  ?)
[...]  ('spongebob',  'sandy')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')

上述查询还使用了 Select.where() 方法添加 WHERE 条件,并且还使用了 SQLAlchemy 类似列的构造中的 ColumnOperators.in_() 方法来使用 SQL IN 操作符。

有关如何选择对象和单独列的更多细节请参见选择 ORM 实体和列。

使用 JOIN 进行 SELECT

在一次性查询多个表格是非常常见的,在 SQL 中,JOIN 关键字是这种情况的主要方式。Select 构造使用 Select.join() 方法创建连接:

代码语言:javascript复制
>>> stmt = (
...     select(Address)
...     .join(Address.user)
...     .where(User.name == "sandy")
...     .where(Address.email_address == "sandy@sqlalchemy.org")
... )
>>> sandy_address = session.scalars(stmt).one()
SELECT  address.id,  address.email_address,  address.user_id
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  ?  AND  address.email_address  =  ?
[...]  ('sandy',  'sandy@sqlalchemy.org')
>>> sandy_address
Address(id=2, email_address='sandy@sqlalchemy.org')

上述查询演示了多个 WHERE 条件的使用,这些条件会自动使用 AND 进行链接,以及如何使用 SQLAlchemy 类似列对象创建“相等性”比较,这使用了重写的 Python 方法 ColumnOperators.__eq__() 来生成 SQL 条件对象。

有关上述概念的更多背景信息在 WHERE 子句和明确的 FROM 子句和 JOIN 处。

进行更改

Session对象与我们的 ORM 映射类UserAddress结合使用,自动跟踪对对象的更改,这些更改将在下次Session flush 时生成 SQL 语句。 在下面,我们更改了与“sandy”关联的一个电子邮件地址,并在发出 SELECT 以检索“patrick”的行后向“patrick”添加了一个新的电子邮件地址:

代码语言:javascript复制
>>> stmt = select(User).where(User.name == "patrick")
>>> patrick = session.scalars(stmt).one()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('patrick',)
>>> patrick.addresses.append(Address(email_address="patrickstar@sqlalchemy.org"))
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (3,)
>>> sandy_address.email_address = "sandy_cheeks@sqlalchemy.org"

>>> session.commit()
UPDATE  address  SET  email_address=?  WHERE  address.id  =  ?
[...]  ('sandy_cheeks@sqlalchemy.org',  2)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)
[...]  ('patrickstar@sqlalchemy.org',  3)
COMMIT 

注意当我们访问patrick.addresses时,会发出一个 SELECT。 这称为延迟加载。 关于使用更多或更少 SQL 访问相关项目的不同方式的背景介绍在加载策略中引入。

有关 ORM 数据操作的详细说明始于使用 ORM 进行数据操作。

一些删除

一切都必须有个了结,就像我们的一些数据库行一样 - 这里是两种不同形式的删除的快速演示,这两种删除根据特定用例的不同而重要。

首先,我们将从sandy用户中删除一个Address对象。 当Session下次 flush 时,这将导致该行被删除。 此行为是我们在映射中配置的称为删除级联的东西。 我们可以使用Session.get()按主键获取sandy对象的句柄,然后使用该对象:

代码语言:javascript复制
>>> sandy = session.get(User, 2)
BEGIN  (implicit)
SELECT  user_account.id  AS  user_account_id,  user_account.name  AS  user_account_name,  user_account.fullname  AS  user_account_fullname
FROM  user_account
WHERE  user_account.id  =  ?
[...]  (2,)
>>> sandy.addresses.remove(sandy_address)
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (2,) 

上面的最后一个 SELECT 是延迟加载操作进行,以便加载sandy.addresses集合,以便我们可以删除sandy_address成员。有其他方法可以完成这一系列操作,这些方法不会生成太多的 SQL。

我们可以选择发出 DELETE SQL,以删除到目前为止已更改的内容,而不提交事务,使用Session.flush()方法:

代码语言:javascript复制
>>> session.flush()
DELETE  FROM  address  WHERE  address.id  =  ?
[...]  (2,) 

接下来,我们将完全删除“patrick”用户。 对于对象本身的顶级删除,我们使用Session.delete()方法; 此方法实际上不执行删除,而是设置对象将在下次 flush 时被删除。 该操作还将根据我们配置的级联选项级联到相关对象,本例中为相关的Address对象:

代码语言:javascript复制
>>> session.delete(patrick)
SELECT  user_account.id  AS  user_account_id,  user_account.name  AS  user_account_name,  user_account.fullname  AS  user_account_fullname
FROM  user_account
WHERE  user_account.id  =  ?
[...]  (3,)
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (3,) 

在这种特殊情况下,Session.delete()方法发出了两个 SELECT 语句,即使它没有发出 DELETE,这可能看起来令人惊讶。这是因为当该方法去检查对象时,发现patrick对象已经过期,这是在我们上次调用Session.commit()时发生的,发出的 SQL 是为了重新从新事务加载行。这种过期是可选的,并且在正常使用中,我们经常会在不适用的情况下关闭它。

为了说明被删除的行,这里是提交:

代码语言:javascript复制
>>> session.commit()
DELETE  FROM  address  WHERE  address.id  =  ?
[...]  (4,)
DELETE  FROM  user_account  WHERE  user_account.id  =  ?
[...]  (3,)
COMMIT 

教程讨论了 ORM 删除,详见使用工作单元模式删除 ORM 对象。对象过期的背景信息在过期/刷新;级联在级联中进行了深入讨论。

深入学习上述概念

对于新用户来说,上面的部分可能是一个快速浏览。上面的每一步中都有许多重要的概念没有涵盖到。通过快速了解事物的外观,建议通过 SQLAlchemy 统一教程逐步学习,以获得对上面所发生的事物的坚实的工作知识。祝你好运!

声明模型

在这里,我们定义了将构成我们从数据库查询的模块级构造。这个结构被称为声明性映射,它一次定义了 Python 对象模型以及描述真实 SQL 表的数据库元数据,这些表存在或将存在于特定数据库中:

代码语言:javascript复制
>>> 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

>>> 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", cascade="all, delete-orphan"
...     )
...
...     def __repr__(self) -> str:
...         return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

>>> 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")
...
...     def __repr__(self) -> str:
...         return f"Address(id={self.id!r}, email_address={self.email_address!r})"

映射始于一个基类,上面称为Base,通过对DeclarativeBase类进行简单的子类化来创建。

通过对Base进行子类化,然后创建个体映射类。一个映射类通常指的是一个特定的数据库表,其名称是通过使用__tablename__类级属性指示的。

接下来,声明表中的列,通过添加包含一个特殊的类型注释称为Mapped的属性来实现。每个属性的名称对应于要成为数据库表的列。每个列的数据类型首先取自与每个Mapped注释相关联的 Python 数据类型;对于 INTEGER 使用 int,对于 VARCHAR 使用 str 等。可选性取决于是否使用了 Optional[] 类型修饰符。可以使用右侧的 SQLAlchemy 类型对象指示更具体的类型信息,例如上面在 User.name 列中使用的 String 数据类型。Python 类型和 SQL 类型之间的关联可以使用 type annotation map 进行定制。

mapped_column() 指令用于所有需要更具体定制的基于列的属性。除了类型信息外,该指令还接受各种参数,指示有关数据库列的特定细节,包括服务器默认值和约束信息,例如主键和外键的成员资格。mapped_column() 指令接受了 SQLAlchemy Column 类接受的参数的超集,该类由 SQLAlchemy Core 用于表示数据库列。

所有的 ORM 映射类都需要至少声明一个列作为主键的一部分,通常是通过在应该成为键的那些mapped_column()对象上使用Column.primary_key参数来实现的。在上面的示例中,User.idAddress.id 列被标记为主键。

综合起来,SQLAlchemy 中一个字符串表名和列声明列表的组合被称为 table metadata。在 SQLAlchemy 统一教程中介绍了使用 Core 和 ORM 方法设置表元数据的方法,在 Working with Database Metadata 章节中。上述映射是 Annotated Declarative Table 配置的示例。

还有其他Mapped的变体可用,最常见的是上面指示的relationship()构造。与基于列的属性相反,relationship()表示两个 ORM 类之间的链接。在上面的示例中,User.addressesUser链接到AddressAddress.userAddress链接到Userrelationship()构造在 SQLAlchemy 统一教程中的使用 ORM 相关对象中进行介绍。

最后,上面的示例类包括一个 __repr__() 方法,虽然不是必需的,但对于调试很有用。映射类可以使用诸如 __repr__() 这样的方法自动生成,使用数据类。有关数据类映射的更多信息,请参阅声明性数据类映射。

创建引擎

Engine是一个能够为我们创建新数据库连接的工厂,它还将连接保留在连接池中以供快速重用。出于学习目的,我们通常使用 SQLite 内存数据库以方便起见:

代码语言:javascript复制
>>> from sqlalchemy import create_engine
>>> engine = create_engine("sqlite://", echo=True)

提示

echo=True 参数表示连接发出的 SQL 将被记录到标准输出。

Engine的全面介绍始于建立连接 - 引擎。

发出 CREATE TABLE DDL

使用我们的表元数据和引擎,我们可以一次性在目标 SQLite 数据库中生成我们的模式,使用一种叫做MetaData.create_all()的方法:

代码语言:javascript复制
>>> Base.metadata.create_all(engine)
BEGIN  (implicit)
PRAGMA  main.table_...info("user_account")
...
PRAGMA  main.table_...info("address")
...
CREATE  TABLE  user_account  (
  id  INTEGER  NOT  NULL,
  name  VARCHAR(30)  NOT  NULL,
  fullname  VARCHAR,
  PRIMARY  KEY  (id)
)
...
CREATE  TABLE  address  (
  id  INTEGER  NOT  NULL,
  email_address  VARCHAR  NOT  NULL,
  user_id  INTEGER  NOT  NULL,
  PRIMARY  KEY  (id),
  FOREIGN  KEY(user_id)  REFERENCES  user_account  (id)
)
...
COMMIT 

刚才我们编写的那段 Python 代码发生了很多事情。要完整了解表元数据的情况,请参阅使用数据库元数据中的教程。

创建对象并持久化

我们现在可以将数据插入到数据库中了。我们通过创建UserAddress类的实例来实现这一点,这些类已经通过声明映射过程自动创建了__init__()方法。然后,我们使用一个称为 Session 的对象将它们传递给数据库,该对象使用Engine与数据库进行交互。这里使用Session.add_all()方法一次添加多个对象,并且将使用Session.commit()方法刷新数据库中的任何待处理更改,然后提交当前的数据库事务,该事务始终在使用Session时处于进行中:

代码语言:javascript复制
>>> from sqlalchemy.orm import Session

>>> with Session(engine) as session:
...     spongebob = User(
...         name="spongebob",
...         fullname="Spongebob Squarepants",
...         addresses=[Address(email_address="spongebob@sqlalchemy.org")],
...     )
...     sandy = User(
...         name="sandy",
...         fullname="Sandy Cheeks",
...         addresses=[
...             Address(email_address="sandy@sqlalchemy.org"),
...             Address(email_address="sandy@squirrelpower.org"),
...         ],
...     )
...     patrick = User(name="patrick", fullname="Patrick Star")
...
...     session.add_all([spongebob, sandy, patrick])
...
...     session.commit()
BEGIN  (implicit)
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('spongebob',  'Spongebob Squarepants')
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy',  'Sandy Cheeks')
INSERT  INTO  user_account  (name,  fullname)  VALUES  (?,  ?)  RETURNING  id
[...]  ('patrick',  'Patrick Star')
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('spongebob@sqlalchemy.org',  1)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy@sqlalchemy.org',  2)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)  RETURNING  id
[...]  ('sandy@squirrelpower.org',  2)
COMMIT 

提示

建议像上面那样使用 Python 的 with: 语句,即使用上下文管理器样式使用SessionSession 对象代表着活跃的数据库资源,所以当一系列操作完成时,确保关闭它是很好的。在下一节中,我们将保持Session处于打开状态,仅用于说明目的。

创建Session的基础知识请参考使用 ORM Session 执行,更多内容请参考使用 Session 的基础知识。

接下来,介绍了一些基本持久化操作的变体,请参阅使用 ORM 工作单元模式插入行。

简单的 SELECT

在数据库中有一些行时,这是发出 SELECT 语句以加载一些对象的最简单形式。要创建 SELECT 语句,我们使用select() 函数创建一个新的Select 对象,然后使用Session 调用它。查询 ORM 对象时经常有用的方法是Session.scalars() 方法,它将返回一个ScalarResult 对象,该对象将迭代我们选择的 ORM 对象:

代码语言:javascript复制
>>> from sqlalchemy import select

>>> session = Session(engine)

>>> stmt = select(User).where(User.name.in_(["spongebob", "sandy"]))

>>> for user in session.scalars(stmt):
...     print(user)
BEGIN  (implicit)
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  IN  (?,  ?)
[...]  ('spongebob',  'sandy')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')

上述查询还使用了Select.where() 方法添加 WHERE 条件,并且还使用了所有 SQLAlchemy 列对象的一部分的ColumnOperators.in_() 方法来使用 SQL IN 操作符。

如何选择对象和单独列的更多详细信息请参阅选择 ORM 实体和列。

使用 JOIN 的 SELECT

在 SQL 中,一次查询多个表是非常常见的,而 JOIN 关键字是实现这一目的的主要方法。Select 构造函数使用Select.join() 方法创建连接:

代码语言:javascript复制
>>> stmt = (
...     select(Address)
...     .join(Address.user)
...     .where(User.name == "sandy")
...     .where(Address.email_address == "sandy@sqlalchemy.org")
... )
>>> sandy_address = session.scalars(stmt).one()
SELECT  address.id,  address.email_address,  address.user_id
FROM  address  JOIN  user_account  ON  user_account.id  =  address.user_id
WHERE  user_account.name  =  ?  AND  address.email_address  =  ?
[...]  ('sandy',  'sandy@sqlalchemy.org')
>>> sandy_address
Address(id=2, email_address='sandy@sqlalchemy.org')

上述查询示例说明了多个 WHERE 条件如何自动使用 AND 连接,并且展示了如何使用 SQLAlchemy 列对象创建“相等性”比较,该比较使用了重载的 Python 方法ColumnOperators.__eq__()来生成 SQL 条件对象。

以上概念的更多背景可在 WHERE 子句和显式 FROM 子句和 JOIN 处找到。

进行更改

Session 对象与我们的 ORM 映射类 UserAddress 一起,会自动跟踪对象的更改,这些更改会导致 SQL 语句在下次 Session 刷新时被发出。下面,我们更改了与“sandy”关联的一个电子邮件地址,并在发出 SELECT 以检索“patrick”的行之后,向“patrick”添加了一个新的电子邮件地址:

代码语言:javascript复制
>>> stmt = select(User).where(User.name == "patrick")
>>> patrick = session.scalars(stmt).one()
SELECT  user_account.id,  user_account.name,  user_account.fullname
FROM  user_account
WHERE  user_account.name  =  ?
[...]  ('patrick',)
>>> patrick.addresses.append(Address(email_address="patrickstar@sqlalchemy.org"))
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (3,)
>>> sandy_address.email_address = "sandy_cheeks@sqlalchemy.org"

>>> session.commit()
UPDATE  address  SET  email_address=?  WHERE  address.id  =  ?
[...]  ('sandy_cheeks@sqlalchemy.org',  2)
INSERT  INTO  address  (email_address,  user_id)  VALUES  (?,  ?)
[...]  ('patrickstar@sqlalchemy.org',  3)
COMMIT 

注意当我们访问patrick.addresses时,会发出一个 SELECT。这被称为延迟加载。有关使用更多或更少的 SQL 访问相关项目的不同方法的背景介绍,请参阅加载器策略。

有关使用 ORM 进行数据操作的详细说明,请参阅 ORM 数据操作。

一些删除操作

万物都有尽头,就像我们的一些数据库行一样 - 这里快速演示了两种不同形式的删除,根据特定用例的重要性而定。

首先,我们将从sandy用户中删除一个Address对象。当Session下次刷新时,这将导致该行被删除。这种行为是我们在映射中配置的,称为级联删除。我们可以使用 Session.get() 按主键获取到sandy对象,然后操作该对象:

代码语言:javascript复制
>>> sandy = session.get(User, 2)
BEGIN  (implicit)
SELECT  user_account.id  AS  user_account_id,  user_account.name  AS  user_account_name,  user_account.fullname  AS  user_account_fullname
FROM  user_account
WHERE  user_account.id  =  ?
[...]  (2,)
>>> sandy.addresses.remove(sandy_address)
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (2,) 

上面的最后一个 SELECT 是为了进行延迟加载 操作,以便加载sandy.addresses集合,以便我们可以删除sandy_address成员。还有其他方法可以执行这一系列操作,不会发出太多的 SQL。

我们可以选择发出针对到目前为止被更改的 DELETE SQL,而不提交事务,使用 Session.flush() 方法:

代码语言:javascript复制
>>> session.flush()
DELETE  FROM  address  WHERE  address.id  =  ?
[...]  (2,) 

接下来,我们将完全删除“patrick”用户。对于对象的顶级删除,我们使用Session.delete()方法;这个方法实际上并不执行删除操作,而是设置对象在下一次刷新时将被删除。该操作还会根据我们配置的级联选项级联到相关对象,本例中是关联的Address对象:

代码语言:javascript复制
>>> session.delete(patrick)
SELECT  user_account.id  AS  user_account_id,  user_account.name  AS  user_account_name,  user_account.fullname  AS  user_account_fullname
FROM  user_account
WHERE  user_account.id  =  ?
[...]  (3,)
SELECT  address.id  AS  address_id,  address.email_address  AS  address_email_address,  address.user_id  AS  address_user_id
FROM  address
WHERE  ?  =  address.user_id
[...]  (3,) 

在这种特殊情况下,Session.delete()方法发出了两个 SELECT 语句,即使它没有发出 DELETE,这可能看起来令人惊讶。这是因为当方法检查对象时,发现patrick对象已经过期,这是在我们上次调用Session.commit()时发生的,发出的 SQL 是为了从新事务重新加载行。这种过期是可选的,在正常使用中,我们通常会在不适用的情况下关闭它。

要说明被删除的行,请看这个提交:

代码语言:javascript复制
>>> session.commit()
DELETE  FROM  address  WHERE  address.id  =  ?
[...]  (4,)
DELETE  FROM  user_account  WHERE  user_account.id  =  ?
[...]  (3,)
COMMIT 

本教程讨论了 ORM 删除操作,详情请见使用工作单元模式删除 ORM 对象。关于对象过期的背景信息请参考过期/刷新;级联操作在 Cascades 中有详细讨论。

深入学习上述概念

对于新用户来说,上述部分可能是一场令人眼花缭乱的旅程。每个步骤中都有许多重要概念没有涵盖。快速了解事物的外观后,建议通过 SQLAlchemy 统一教程来深入了解上述内容。祝好运!

ORM 映射类配置

原文:docs.sqlalchemy.org/en/20/orm/mapper_config.html

ORM 配置的详细参考,不包括关系,关系详细说明在关系配置。

要快速查看典型的 ORM 配置,请从 ORM 快速入门开始。

要了解 SQLAlchemy 实现的对象关系映射概念,请先查看 SQLAlchemy 统一教程,在使用 ORM 声明形式定义表元数据中介绍。

  • ORM 映射类概述
    • ORM 映射风格
      • 声明性映射
      • 命令式映射
    • 映射类基本组件
      • 待映射的类
      • 表或其他来自子句对象
      • 属性字典
      • 其他映射器配置参数
    • 映射类行为
      • 默认构造函数
      • 跨加载保持非映射状态
      • 映射类、实例和映射器的运行时内省
        • 映射器对象的检查
        • 映射实例的检查
  • 使用声明性映射类
    • 声明性映射风格
      • 使用声明性基类
      • 使用装饰器的声明性映射(无声明性基类)
    • 使用声明性配置表
      • 带有 mapped_column() 的声明性表
        • 使用带注释的声明性表(mapped_column()的类型注释形式)
        • 访问表和元数据
        • 声明性表配置
        • 使用声明性表的显式模式名称
        • 为声明式映射的列设置加载和持久化选项
        • 显式命名声明式映射列
        • 将额外列添加到现有的声明式映射类
      • 使用命令式表进行声明式(即混合声明式)
        • 映射表列的替代属性名
        • 为命令式表列应用加载、持久化和映射选项
      • 使用反射表进行声明式映射
        • 使用延迟反射
        • 使用自动映射
        • 从反射表自动化列命名方案
        • 映射到显式主键列集合
        • 映射表列的子集
    • 声明式映射器配置
      • 使用声明式定义映射属性
      • 声明式的映射器配置选项
        • 动态构建映射器参数
      • 其他声明式映射指令
        • __declare_last__()
        • __declare_first__()
        • metadata
        • __abstract__
        • __table_cls__
    • 使用混合组合映射层次结构
      • 增强基类
      • 混合使用列
      • 混合使用关系
      • _orm.column_property() 和其他 _orm.MapperProperty 类中混合使用
      • 使用混合和基类进行映射继承模式
        • 使用 _orm.declared_attr() 与继承 TableMapper 参数
        • 使用 _orm.declared_attr() 生成表特定的继承列
      • 从多个混合类组合表/映射器参数
      • 使用命名约定在混合类上创建索引和约束
  • 与 dataclasses 和 attrs 集成
    • 声明式数据类映射
      • 类级别功能配置
      • 属性配置
        • 列默认值
        • 与 Annotated 集成
      • 使用混合类和抽象超类
      • 关系配置
      • 使用未映射的数据类字段
      • 与 Pydantic 等替代数据类提供者集成
    • 将 ORM 映射应用于现有的数据类(传统数据类使用)
      • 使用声明式与命令式表映射映射预先存在的数据类
      • 使用声明式样式字段映射预先存在的数据类
        • 使用预先存在的数据类的声明式混合类
      • 使用命令式映射映射预先存在的数据类
    • 将 ORM 映射应用于现有的 attrs 类
      • 使用声明式“命令式表”映射映射属性
      • 使用命令式映射映射属性
  • SQL 表达式作为映射属性
    • 使用混合类
    • 使用 column_property
      • 将 column_property() 添加到现有的声明式映射类
      • 在映射时从列属性组合
      • 使用 column_property() 进行列推迟
    • 使用普通描述符
    • 查询时将 SQL 表达式作为映射属性
  • 更改属性行为
    • 简单验证器
      • validates()
    • 在核心级别使用自定义数据类型
    • 使用描述符和混合物
    • 同义词
      • synonym()
    • 操作符定制
  • 复合列类型
    • 使用映射的复合列类型
    • 复合体的其他映射形式
      • 直接映射列,然后传递给复合体
      • 直接映射列,将属性名称传递给复合体
      • 命令映射和命令表
    • 使用传统非数据类
    • 跟踪复合体上的原位变化
    • 重新定义复合体的比较操作
    • 嵌套复合体
    • 复合体 API
      • composite()
  • 映射类继承层次结构
    • 联接表继承
      • 与联接继承相关的关系
      • 加载联接继承映射
    • 单表继承
      • 使用 use_existing_column 解决列冲突
      • 与单表继承相关的关系
      • 使用 polymorphic_abstract 构建更深层次的层次结构
      • 加载单表继承映射
    • 具体表继承
      • 具体多态加载配置
      • 抽象具体类
      • 经典和半经典具体多态配置
      • 具体继承关系的关系
      • 加载具体继承映射
  • 非传统映射
    • 将类映射到多个表
    • 将类映射到任意子查询
    • 一个类的多个映射器
  • 配置版本计数器
    • 简单版本计数
    • 自定义版本计数器/类型
    • 服务器端版本计数器
    • 编程或条件版本计数器
  • 类映射 API
    • registry
      • registry.__init__()
      • registry.as_declarative_base()
      • registry.configure()
      • registry.dispose()
      • registry.generate_base()
      • registry.map_declaratively()
      • registry.map_imperatively()
      • registry.mapped()
      • registry.mapped_as_dataclass()
      • registry.mappers
      • registry.update_type_annotation_map()
    • add_mapped_attribute()
    • column_property()
    • declarative_base()
    • declarative_mixin()
    • as_declarative()
    • mapped_column()
    • declared_attr
      • declared_attr.cascading
      • declared_attr.directive
    • DeclarativeBase
      • DeclarativeBase.__mapper__
      • DeclarativeBase.__mapper_args__
      • DeclarativeBase.__table__
      • DeclarativeBase.__table_args__
      • DeclarativeBase.__tablename__
      • DeclarativeBase.metadata
      • DeclarativeBase.registry
    • DeclarativeBaseNoMeta
      • DeclarativeBaseNoMeta.__mapper__
      • DeclarativeBaseNoMeta.__mapper_args__
      • DeclarativeBaseNoMeta.__table__
      • DeclarativeBaseNoMeta.__table_args__
      • DeclarativeBaseNoMeta.__tablename__
      • DeclarativeBaseNoMeta.metadata
      • DeclarativeBaseNoMeta.registry
    • has_inherited_table()
    • synonym_for()
    • object_mapper()
    • class_mapper()
    • configure_mappers()
    • clear_mappers()
    • identity_key()
    • polymorphic_union()
    • orm_insert_sentinel()
    • reconstructor()
    • Mapper
      • Mapper.__init__()
      • Mapper.add_properties()
      • Mapper.add_property()
      • Mapper.all_orm_descriptors
      • Mapper.attrs
      • Mapper.base_mapper
      • Mapper.c
      • Mapper.cascade_iterator()
      • Mapper.class_
      • Mapper.class_manager
      • Mapper.column_attrs
      • Mapper.columns
      • Mapper.common_parent()
      • Mapper.composites
      • Mapper.concrete
      • Mapper.configured
      • Mapper.entity
      • Mapper.get_property()
      • Mapper.get_property_by_column()
      • Mapper.identity_key_from_instance()
      • Mapper.identity_key_from_primary_key()
      • Mapper.identity_key_from_row()
      • Mapper.inherits
      • Mapper.is_mapper
      • Mapper.is_sibling()
      • Mapper.isa()
      • Mapper.iterate_properties
      • Mapper.local_table
      • Mapper.mapped_table
      • Mapper.mapper
      • Mapper.non_primary
      • Mapper.persist_selectable
      • Mapper.polymorphic_identity
      • Mapper.polymorphic_iterator()
      • Mapper.polymorphic_map
      • Mapper.polymorphic_on
      • Mapper.primary_key
      • Mapper.primary_key_from_instance()
      • Mapper.primary_mapper()
      • Mapper.relationships
      • Mapper.selectable
      • Mapper.self_and_descendants
      • Mapper.single
      • Mapper.synonyms
      • Mapper.tables
      • Mapper.validators
      • Mapper.with_polymorphic_mappers
    • MappedAsDataclass
    • MappedClassProtocol

ORM 映射类概述

原文:docs.sqlalchemy.org/en/20/orm/mapping_styles.html

ORM 类映射配置概述。

对于对 SQLAlchemy ORM 和/或对 Python 比较新的读者来说,建议浏览 ORM 快速入门,最好是通过 SQLAlchemy 统一教程进行学习,其中首次介绍了 ORM 配置,即使用 ORM 声明形式定义表元数据。

ORM 映射风格

SQLAlchemy 具有两种不同的映射器配置风格,然后具有更多的子选项来设置它们。映射器风格的可变性存在是为了适应各种开发人员偏好的列表,包括用户定义的类与如何映射到关系模式表和列之间的抽象程度,正在使用的类层次结构的种类,包括是否存在自定义元类方案,最后,是否同时存在其他类实例化方法,例如是否同时使用 Python dataclasses。

在现代 SQLAlchemy 中,这些风格之间的差异基本上是表面的;当使用特定的 SQLAlchemy 配置风格来表达映射类的意图时,映射类的内部映射过程大部分都是相同的,最终的结果始终是一个用户定义的类,其配置了针对可选择单元的Mapper,通常由Table对象表示,并且该类本身已经被 instrumented 以包括与关系操作相关的行为,无论是在类的级别还是在该类的实例上。由于过程在所有情况下基本上都是相同的,因此从不同风格映射的类始终是完全可互操作的。协议MappedClassProtocol可用于在使用诸如 mypy 等类型检查器时指示映射类。

原始的映射 API 通常被称为“经典”风格,而更自动化的映射风格称为“声明”风格。SQLAlchemy 现在将这两种映射风格称为命令式映射声明式映射

无论使用何种映射样式,截至 SQLAlchemy 1.4 版本,所有 ORM 映射都源自一个名为registry的单个对象,它是映射类的注册表。使用此注册表,一组映射器配置可以作为一个组进行最终确定,并且在特定注册表内的类可以在配置过程中相互通过名称引用。

自 1.4 版本更改:声明式和经典映射现在被称为“声明式”和“命令式”映射,并在内部统一,都源自代表一组相关映射的registry 构造。

声明式映射

声明式映射是现代 SQLAlchemy 中构建映射的典型方式。最常见的模式是首先使用DeclarativeBase 超类构建一个基类。生成的基类,当被子类化时,将对从它派生的所有子类应用声明式映射过程,相对于默认情况下新基类的本地registry。下面的示例演示了使用声明基类,然后在声明表映射中使用它:

代码语言:javascript复制
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

# declarative base class
class Base(DeclarativeBase):
    pass

# an example mapping using the base
class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str] = mapped_column(String(30))
    nickname: Mapped[Optional[str]]

在上面的示例中,DeclarativeBase 类用于生成一个新的基类(在 SQLAlchemy 文档中通常被称为 Base,但可以有任何所需名称),从中新类可以继承映射,如上所示,构建了一个新的映射类 User

自 2.0 版本更改:DeclarativeBase 超类取代了declarative_base() 函数和registry.generate_base() 方法的使用;超类方法与PEP 484 工具集成,无需使用插件。有关迁移说明,请参阅 ORM 声明模型。

基类指的是维护一组相关映射类的registry对象,以及保留映射到类的一组Table对象的MetaData对象。

主要的声明式映射样式在以下各节中进一步详细说明:

  • 使用声明基类 - 使用基类进行声明式映射。
  • 使用装饰器的声明式映射(无声明基类) - 使用装饰器进行声明式映射,而不是使用基类。

在声明式映射类的范围内,Table 元数据的声明方式也有两种变体。包括:

  • 使用 mapped_column() 的声明式表 - 在映射类内联声明表列,使用 mapped_column() 指令(或在传统形式中,直接使用 Column 对象)。mapped_column() 指令也可以选择性地与使用 Mapped 类的类型注解结合,该类可以直接提供有关映射列的一些详细信息。列指令,结合 __tablename__ 和可选的 __table_args__ 类级指令,将允许声明式映射过程构建要映射的 Table 对象。
  • 具有命令式表的声明式(又名混合声明式) - 不是单独指定表名和属性,而是将显式构建的 Table 对象与以其他方式进行声明式映射的类关联。这种映射风格是“声明式”和“命令式”映射的混合,并适用于将类映射到反射的 Table 对象,以及将类映射到现有的 Core 构造,如连接和子查询。

声明式映射的文档继续在 使用声明式映射映射类 中。### 命令式映射

命令式经典映射是指使用 registry.map_imperatively() 方法配置映射类的情况,其中目标类不包含任何声明类属性。

提示

命令式映射形式是 SQLAlchemy 最早发布的版本中源自的较少使用的一种映射形式。它本质上是一种绕过声明式系统提供更“基础”的映射系统的方法,并且不提供像PEP 484支持这样的现代特性。因此,大多数文档示例都使用声明式形式,并建议新用户从声明式表配置开始。

在 2.0 版本中更改:现在使用registry.map_imperatively()方法来创建经典映射。sqlalchemy.orm.mapper()独立函数已被有效移除。

在“经典”形式中,表的元数据是分别用Table构造创建的,然后通过registry.map_imperatively()方法与User类关联,在建立registry实例后。通常,一个registry的单个实例被共享给所有彼此相关的映射类:

代码语言:javascript复制
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String(50)),
    Column("nickname", String(12)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

提供关于映射属性的信息,比如与其他类的关系,通过properties字典提供。下面的示例说明了第二个Table对象,映射到一个名为Address的类,然后通过relationship()User关联:

代码语言:javascript复制
address = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.id")),
    Column("email_address", String(50)),
)

mapper_registry.map_imperatively(
    User,
    user,
    properties={
        "addresses": relationship(Address, backref="user", order_by=address.c.id)
    },
)

mapper_registry.map_imperatively(Address, address)

注意,使用命令式方法映射的类与使用声明式方法映射的类完全可以互换。这两种系统最终都会创建相同的配置,包括一个Table、用户定义的类,以及一个Mapper对象。当我们谈论“Mapper的行为”时,这也包括了使用声明式系统时——它仍然被使用,只是在幕后进行。

对于所有的映射形式,可以通过传递构造参数来配置类的映射,这些构造参数最终成为Mapper对象的一部分,通过它的构造函数传递。传递给Mapper的参数来自给定的映射形式,包括传递给 Imperative 映射的registry.map_imperatively()的参数,或者在使用声明式系统时,来自被映射的表列、SQL 表达式和关系以及 mapper_args 等属性的组合。

Mapper类寻找的配置信息大致可以分为四类:

要映射的类

这是我们应用程序中构建的类。通常情况下,这个类的结构没有限制。[1] 当映射一个 Python 类时,对于这个类只能有一个Mapper对象。[2]

当使用声明式映射样式进行映射时,要映射的类要么是声明基类的子类,要么由装饰器或函数(如registry.mapped())处理。

当使用命令式映射样式进行映射时,类直接作为map_imperatively.class_参数传递。

表或其他来自子句对象

在绝大多数常见情况下,这是Table的实例。对于更高级的用例,它也可以指任何一种FromClause对象,最常见的替代对象是SubqueryJoin对象。

当使用声明式映射样式时,主题表格是根据__tablename__属性和所提供的Column对象,由声明式系统生成的,或者是通过__table__属性建立的。这两种配置样式分别在使用 mapped_column()定义声明式表格和命令式表格与声明式(又名混合声明式)中介绍。

当使用命令式样式进行映射时,主题表格作为map_imperatively.local_table参数位置传递。

与映射类“每个类一个映射器”的要求相反,用于映射的Table或其他FromClause对象可以与任意数量的映射关联。Mapper直接对用户定义的类应用修改,但不以任何方式修改给定的Table或其他FromClause

属性字典

这是与映射类关联的所有属性的字典。默认情况下,Mapper根据给定的Table从中派生出这个字典的条目,以ColumnProperty对象的形式表示,每个对象引用映射表的单个Column。属性字典还将包含所有其他类型的要配置的MapperProperty对象,最常见的是由relationship()构造生成的实例。

当使用声明式映射样式进行映射时,属性字典由声明式系统通过扫描要映射的类生成。有关此过程的说明,请参阅使用声明式定义映射属性部分。

当使用命令式风格进行映射时,属性字典直接作为properties参数传递给registry.map_imperatively(),该方法将将其传递给Mapper.properties参数。

其他映射器配置参数

当使用声明式映射风格进行映射时,额外的映射器配置参数通过__mapper_args__类属性进行配置。使用示例可在声明式映射器配置选项处找到。

当使用命令式风格进行映射时,关键字参数传递给registry.map_imperatively()方法,该方法将其传递给Mapper类。

可接受的所有参数范围在Mapper中有文档记录。## 映射类行为

使用registry对象进行所有映射风格时,以下行为是共同的:

默认构造函数

registry将默认构造函数,即__init__方法,应用于所有未明确具有自己__init__方法的映射类。该方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:

代码语言:javascript复制
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"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str]

上述User类型的对象将具有一个构造函数,允许像这样创建User对象:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

提示

声明式数据类映射功能提供了一种通过使用 Python 数据类生成默认__init__()方法的替代方法,并允许高度可配置的构造函数形式。

警告

当对象在 Python 代码中构造时,仅在调用类的__init__()方法时才会调用__init__()方法,而不是在从数据库加载或刷新对象时。请参阅下一节在加载时保持非映射状态了解如何在加载对象时调用特殊逻辑的基础知识。

包含显式__init__()方法的类将保留该方法,并且不会应用默认构造函数。

要更改使用的默认构造函数,可以向registry.constructor参数提供用户定义的 Python 可调用对象,该对象将用作默认构造函数。

构造函数也适用于命令式映射:

代码语言:javascript复制
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

上述类,如 命令式映射 中所述的那样被命令式映射,还将具有与 registry 关联的默认构造函数。

从版本 1.4 开始:经典映射现在支持通过 registry.map_imperatively() 方法进行映射时的标准配置级构造函数。### 在加载过程中保持非映射状态

当对象直接在 Python 代码中构造时,会调用映射类的 __init__() 方法:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

但是,当使用 ORM Session 加载对象时,会调用 __init__() 方法:

代码语言:javascript复制
u1 = session.scalars(select(User).where(User.name == "some name")).first()

这样做的原因是,从数据库加载时,用于构造对象的操作,如上例中的 User,更类似于反序列化,比如反序列化,而不是初始构造。对象的大部分重要状态不是首次组装,而是重新从数据库行加载。

因此,为了在对象内部维护不属于存储到数据库的数据的状态,使得当对象加载和构造时都存在这些状态,有两种通用方法如下所述。

使用 Python 描述符,比如 @property,而不是状态,根据需要动态计算属性。

对于简单的属性,这是最简单的方法,也是最不容易出错的方法。例如,如果一个对象 PointPoint.xPoint.y,想要一个这些属性的和的属性:

代码语言:javascript复制
class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    @property
    def x_plus_y(self):
        return self.x   self.y

使用动态描述符的优点是值每次都会计算,这意味着它会根据底层属性(在本例中为 xy)的更改来维护正确的值。

其他形式的上述模式包括 Python 标准库cached_property 装饰器(它是缓存的,并且不会每次重新计算),以及 SQLAlchemy 的hybrid_property 装饰器,允许属性在 SQL 查询中使用。

使用 InstanceEvents.load() 来在加载时建立状态,并可选地使用补充方法 InstanceEvents.refresh()InstanceEvents.refresh_flush()

这些是在对象从数据库加载或在过期后刷新时调用的事件钩子。通常只需要InstanceEvents.load(),因为非映射的本地对象状态不受过期操作的影响。修改上面的Point示例如下所示:

代码语言:javascript复制
from sqlalchemy import event

class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    def __init__(self, x, y, **kw):
        super().__init__(x=x, y=y, **kw)
        self.x_plus_y = x   y

@event.listens_for(Point, "load")
def receive_load(target, context):
    target.x_plus_y = target.x   target.y

如果还要使用刷新事件,可以根据需要将事件钩子叠加在一个可调用对象上,如下所示:

代码语言:javascript复制
@event.listens_for(Point, "load")
@event.listens_for(Point, "refresh")
@event.listens_for(Point, "refresh_flush")
def receive_load(target, context, attrs=None):
    target.x_plus_y = target.x   target.y

上面,attrs属性将出现在refreshrefresh_flush事件中,并指示正在刷新的属性名称列表。### 映射类、实例和映射器的运行时内省

使用registry映射的类也将包含一些对所有映射共通的属性:

__mapper__属性将引用与该类相关联的Mapper

代码语言:javascript复制
mapper = User.__mapper__

这个Mapper也是使用inspect()函数对映射类进行检查时返回的对象:

代码语言:javascript复制
from sqlalchemy import inspect

mapper = inspect(User)

__table__属性将引用与该类映射的Table,或更一般地,将引用FromClause对象:

代码语言:javascript复制
table = User.__table__

这个FromClause也是在使用Mapper.local_table属性时返回的对象Mapper

代码语言:javascript复制
table = inspect(User).local_table

对于单表继承映射,其中类是没有自己的表的子类,Mapper.local_table属性以及.__table__属性将为None。要检索在查询此类时实际选择的“可选择”对象,可以通过Mapper.selectable属性获取:

代码语言:javascript复制
table = inspect(User).selectable
映射器对象的检查

如前一节所示,无论使用何种方法,Mapper对象都可以从任何映射类中获取,使用运行时检查 API 系统。使用inspect()函数,可以从映射类获取Mapper

代码语言:javascript复制
>>> from sqlalchemy import inspect
>>> insp = inspect(User)

可用的详细信息包括Mapper.columns

代码语言:javascript复制
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

这是一个可以以列表格式或单个名称查看的命名空间:

代码语言:javascript复制
>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

其他命名空间包括Mapper.all_orm_descriptors,其中包括所有映射属性以及混合属性,关联代理:

代码语言:javascript复制
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

以及Mapper.column_attrs

代码语言:javascript复制
>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

另请参阅

Mapper #### Inspection of Mapped Instances

inspect()函数还提供有关映射类的实例的信息。当应用于映射类的实例时,而不是类本身时,返回的对象被称为InstanceState,它将提供链接到不仅是类使用的Mapper的详细接口,还提供有关实例内个别属性状态的信息,包括它们当前的值以及这如何与它们的数据库加载值相关联。

给定从数据库加载的User类的实例:

代码语言:javascript复制
>>> u1 = session.scalars(select(User)).first()

inspect()函数会返回一个InstanceState对象给我们:

代码语言:javascript复制
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

通过这个对象,我们可以看到诸如Mapper等元素:

代码语言:javascript复制
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

对象所附加到的Session(如果有的话):

代码语言:javascript复制
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

关于对象的当前 persistence state 的信息:

代码语言:javascript复制
>>> insp.persistent
True
>>> insp.pending
False

属性状态信息,如尚未加载或延迟加载的属性(假设addresses指的是映射类上到相关类的relationship()):

代码语言:javascript复制
>>> insp.unloaded
{'addresses'}

有关属性的当前 Python 状态的信息,例如自上次刷新以来未经修改的属性:

代码语言:javascript复制
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

以及自上次刷新以来对属性进行的修改的特定历史记录:

代码语言:javascript复制
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])

另请参阅

InstanceState

InstanceState.attrs

AttributeState ## ORM Mapping Styles

SQLAlchemy 提供了两种不同风格的映射配置,然后进一步提供了设置它们的子选项。映射样式的可变性存在是为了适应开发者偏好的多样性,包括用户定义类与如何映射到关系模式表和列之间的抽象程度,使用的类层次结构种类,包括是否存在自定义元类方案,以及是否同时使用了其他类内部操作方法,例如是否同时使用了 Python dataclasses。

在现代 SQLAlchemy 中,这些风格之间的区别主要是表面的;当使用特定的 SQLAlchemy 配置风格来表达映射类的意图时,映射类的内部映射过程在大多数情况下是相同的,最终的结果总是一个用户定义的类,该类已经针对可选择的单元(通常由一个Table对象表示)配置了一个Mapper,并且该类本身已经被 instrumented 以包括与关系操作相关的行为,既在类的级别,也在该类的实例上。由于在所有情况下该过程基本相同,从不同风格映射的类始终是完全可互操作的。当使用诸如 mypy 等类型检查器时,可以使用协议MappedClassProtocol来指示已映射的类。

最初的映射 API 通常被称为“古典”风格,而更自动化的映射风格则被称为“声明式”风格。SQLAlchemy 现在将这两种映射风格称为命令式映射声明式映射

无论使用何种映射样式,自 SQLAlchemy 1.4 起,所有 ORM 映射都源自一个名为registry的单个对象,它是一组映射类的注册表。使用此注册表,一组映射配置可以作为一个组完成,并且在配置过程中,特定注册表中的类可以通过名称相互引用。

在 1.4 版本中更改:声明式和古典映射现在被称为“声明式”和“命令式”映射,并在内部统一,所有这些都源自代表相关映射的registry 构造。

声明式映射

Declarative Mapping 是在现代 SQLAlchemy 中构建映射的典型方式。最常见的模式是首先使用 DeclarativeBase 超类构造一个基类。结果基类,当被子类继承时,将对所有从它继承的子类应用声明式映射过程,相对于默认情况下新基类的本地 registry。下面的示例说明了使用声明基类然后在声明式表映射中使用它的方法:

代码语言:javascript复制
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

# declarative base class
class Base(DeclarativeBase):
    pass

# an example mapping using the base
class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str] = mapped_column(String(30))
    nickname: Mapped[Optional[str]]

上文中,DeclarativeBase 类用于生成一个新的基类(在 SQLAlchemy 的文档中通常称为 Base,但可以使用任何想要的名称),新的映射类可以从中继承,就像上面构造了一个新的映射类 User 一样。

从 2.0 版本开始更改:DeclarativeBase 超类取代了 declarative_base() 函数和 registry.generate_base() 方法的使用;超类方法与 PEP 484 工具集成,无需使用插件。有关迁移说明,请参阅 ORM Declarative Models。

基类指的是一个维护一组相关映射类的 registry 对象,以及一个保留了一组将类映射到其中的 Table 对象的 MetaData 对象。

主要的 Declarative 映射风格在以下各节中进一步详细说明:

  • 使用声明基类 - 使用基类进行声明式映射。
  • 使用装饰器进行声明式映射(无声明基类) - 使用装饰器进行声明式映射,而不是使用基类。

在 Declarative 映射类的范围内,还有两种声明 Table 元数据的方式。它们包括:

  • mapped_column()的声明式表 - 在映射类内联声明表列,使用mapped_column()指令(或者在遗留形式中,直接使用Column对象)。mapped_column()指令也可以选择性地与类型注解结合使用,使用Mapped类可以直接提供有关映射列的一些细节。列指令与__tablename__和可选的__table_args__类级指令的组合将允许声明式映射过程构造一个要映射的Table对象。
  • 声明式与命令式表(也称为混合声明式) - 不是分别指定表名和属性,而是将明确构造的Table对象与否则以声明方式映射的类相关联。这种映射风格是“声明式”和“命令式”映射的混合体,并适用于将类映射到反射的Table对象,以及将类映射到现有 Core 构造,如连接和子查询。

声明式映射的文档继续在使用声明性映射类 ### 命令式映射

命令式经典映射是指使用registry.map_imperatively()方法配置映射类的配置,其中目标类不包含任何声明式类属性。

提示

命令式映射形式是 SQLAlchemy 在 2006 年的最初版本中少用的一种映射形式。它本质上是绕过声明式系统提供一种更“精简”的映射系统,不提供现代特性,如PEP 484支持。因此,大多数文档示例使用声明式形式,并建议新用户从声明式表配置开始。

在 2.0 版中更改:现在使用registry.map_imperatively()方法创建经典映射。sqlalchemy.orm.mapper()独立函数被有效删除。

在“经典”形式中,表元数据是分别使用Table构造创建的,然后通过registry.map_imperatively()方法与User类关联,在建立registry实例之后。通常,一个registry的单个实例共享所有彼此相关的映射类:

代码语言:javascript复制
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String(50)),
    Column("nickname", String(12)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

关于映射属性的信息,例如与其他类的关系,通过properties字典提供。下面的示例说明了第二个Table对象,映射到名为Address的类,然后通过relationship()链接到User:

代码语言:javascript复制
address = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.id")),
    Column("email_address", String(50)),
)

mapper_registry.map_imperatively(
    User,
    user,
    properties={
        "addresses": relationship(Address, backref="user", order_by=address.c.id)
    },
)

mapper_registry.map_imperatively(Address, address)

请注意,使用命令式方法映射的类与使用声明式方法映射的类完全可互换。两个系统最终创建相同的配置,由一个Table、用户定义类和一个Mapper对象组成。当我们谈论“Mapper的行为”时,这也包括在使用声明式系统时 - 它仍然被使用,只是在幕后。 ### 声明式映射

声明式映射是在现代 SQLAlchemy 中构建映射的典型方式。最常见的模式是首先使用DeclarativeBase超类构造基类。生成的基类,在其派生的所有子类中应用声明式映射过程,相对于一个默认情况下局部于新基类的registry。下面的示例说明了使用声明基类的情况,然后在声明表映射中使用它:

代码语言:javascript复制
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

# declarative base class
class Base(DeclarativeBase):
    pass

# an example mapping using the base
class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str] = mapped_column(String(30))
    nickname: Mapped[Optional[str]]

上面,DeclarativeBase类用于生成一个新的基类(在 SQLAlchemy 的文档中通常称为Base,但可以有任何所需的名称),从中新映射类User构造。

从版本 2.0 开始更改:DeclarativeBase超类取代了declarative_base()函数和registry.generate_base()方法的使用;超类方法集成了PEP 484工具,无需使用插件。有关迁移说明,请参阅 ORM 声明性模型。

基类指的是维护一组相关映射类的registry对象,以及维护一组映射到这些类的Table对象的MetaData对象。

主要的声明性映射样式在以下各节中进一步详细说明:

  • 使用声明性基类 - 使用基类的声明性映射。
  • 使用装饰器进行声明性映射(无声明性基类) - 使用装饰器而不是基类的声明性映射。

在声明性映射类的范围内,还有两种Table元数据声明的方式。这些包括:

  • 使用mapped_column()声明的声明性表格 - 表格列在映射类中使用mapped_column()指令内联声明(或者在传统形式中,直接使用Column对象)。mapped_column()指令还可以选择性地与使用Mapped类进行类型注释,该类可以直接提供有关映射列的一些详细信息相结合。列指令与__tablename__以及可选的__table_args__类级别指令的组合将允许声明性映射过程构造一个要映射的Table对象。
  • 声明式与命令式表格(即混合声明式) - 不是单独指定表名和属性,而是将显式构建的Table对象与在其他情况下以声明方式映射的类关联起来。这种映射方式是“声明式”和“命令式”映射的混合体,适用于诸如将类映射到反射的Table对象,以及将类映射到现有的 Core 构造,如联接和子查询的技术。

声明式映射的文档继续在用声明式映射类中。

命令式映射

命令式经典映射是指使用registry.map_imperatively()方法配置映射类的一种方法,其中目标类不包含任何声明式类属性。

提示

命令式映射形式是 SQLAlchemy 最早期发布的较少使用的映射形式。它基本上是绕过声明式系统提供更“简化”的映射系统,并且不提供现代特性,例如PEP 484支持。因此,大多数文档示例使用声明式形式,建议新用户从声明式表格配置开始。

在 2.0 版更改:registry.map_imperatively() 方法现在用于创建经典映射。sqlalchemy.orm.mapper() 独立函数已被有效移除。

在“经典”形式中,表元数据是使用Table构造单独创建的,然后通过registry.map_imperatively()方法与User类关联,在建立 registry 实例后。通常,一个registry实例共享给所有彼此相关的映射类:

代码语言:javascript复制
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String(50)),
    Column("nickname", String(12)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

映射属性的信息,如与其他类的关系,通过properties字典提供。下面的示例说明了第二个Table对象,映射到名为Address的类,然后通过relationship()User关联:

代码语言:javascript复制
address = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", Integer, ForeignKey("user.id")),
    Column("email_address", String(50)),
)

mapper_registry.map_imperatively(
    User,
    user,
    properties={
        "addresses": relationship(Address, backref="user", order_by=address.c.id)
    },
)

mapper_registry.map_imperatively(Address, address)

注意,使用命令式方法映射的类与使用声明式方法映射的类完全可互换。这两种系统最终都创建相同的配置,包括一个由Table、用户定义类和一个与之关联的Mapper对象组成的配置。当我们谈论“Mapper的行为”时,这也包括使用声明式系统 - 它仍然在幕后使用。

映射类的基本组件

通过所有映射形式,通过传递最终成为Mapper对象的构造参数,可以通过多种方式配置类的映射。传递给Mapper的参数来自给定的映射形式,包括传递给registry.map_imperatively()的参数,用于命令式映射,或者使用声明式系统时,来自与被映射的表列、SQL 表达式和关系相关联的参数以及属性的参数,如 mapper_args

Mapper类寻找的四类常规配置信息如下:

待映射的类

这是我们在应用程序中构造的类。通常情况下,这个类的结构没有限制。[1]当一个 Python 类被映射时,该类只能有一个Mapper对象。[2]

当使用声明式映射风格时,要映射的类要么是声明基类的子类,要么由装饰器或函数处理,如registry.mapped()

当使用命令式映射风格时,类直接作为map_imperatively.class_参数传递。

表格或其他来源子句对象

在绝大多数常见情况下,这是Table的实例。对于更高级的用例,它还可以指代任何一种FromClause对象,最常见的替代对象是SubqueryJoin对象。

当使用声明性映射样式进行映射时,主题表格要么是由声明性系统基于__tablename__属性和所呈现的Column对象生成的,要么是通过__table__属性建立的。这两种配置样式分别在具有映射列的声明性表格和具有命令式表格的声明性(又名混合声明性)中呈现。

当使用命令式样式进行映射时,主题表格作为map_imperatively.local_table参数按位置传递。

与映射类的“每个类一个映射器”的要求相反,作为映射主题的Table或其他FromClause对象可以与任意数量的映射关联。Mapper直接对用户定义的类进行修改,但不以任何方式修改给定的Table或其他FromClause

属性字典

这是将与映射类关联的所有属性关联起来的字典。默认情况下,Mapper 从给定的Table生成此字典的条目,形式为每个都引用映射表的单个ColumnColumnProperty对象。属性字典还将包含所有其他需要配置的MapperProperty对象,最常见的是通过relationship()构造函数生成的实例。

当使用声明式映射样式进行映射时,属性字典是由声明式系统通过扫描要映射的类以获取适当属性而生成的。请参阅使用声明式定义映射属性部分以获取有关此过程的说明。

当使用命令式映射样式进行映射时,属性字典直接作为properties参数传递给registry.map_imperatively(),该参数将其传递给Mapper.properties参数。

其他映射器配置参数

当使用声明式映射样式进行映射时,附加的映射器配置参数通过__mapper_args__类属性进行配置。使用示例请参见使用声明式定义的映射器配置选项。

当使用命令式映射样式进行映射时,关键字参数传递给registry.map_imperatively()方法,该方法将其传递给Mapper类。

接受的全部参数范围在Mapper中有文档记录。

要映射的类

这是我们应用程序中构建的一个类。通常情况下,此类的结构没有任何限制。[1] 当映射 Python 类时,该类只能有一个Mapper对象。[2]

当使用声明式映射风格进行映射时,要映射的类是声明基类的子类,或者由装饰器或函数(如registry.mapped())处理。

当使用命令式风格进行映射时,类直接作为map_imperatively.class_参数传递。

表或其他来自子句对象

在绝大多数常见情况下,这是一个Table的实例。对于更高级的用例,它也可能指的是任何类型的FromClause对象,最常见的替代对象是SubqueryJoin对象。

当使用声明式映射风格进行映射时,主题表通过声明系统基于__tablename__属性和提供的Column对象生成,或者通过__table__属性建立。这两种配置样式在使用 mapped_column() 进行声明性表配置和具有命令式表的声明式(也称为混合声明式)中介绍。

当使用命令式风格进行映射时,主题表作为map_imperatively.local_table参数按位置传递。

与映射类“每个类一个映射器”的要求相反,映射的Table或其他FromClause对象可以与任意数量的映射相关联。Mapper直接将修改应用于用户定义的类,但不以任何方式修改给定的Table或其他FromClause

属性字典

这是一个与映射类相关联的所有属性的字典。默认情况下,Mapper从给定的Table中派生此字典的条目,形成每个映射表的ColumnColumnProperty对象。属性字典还将包含要配置的所有其他种类的MapperProperty对象,最常见的是由relationship()构造生成的实例。

当使用声明性映射风格进行映射时,属性字典由声明性系统通过扫描要映射的类以找到合适的属性而生成。有关此过程的说明,请参见使用声明性定义映射属性部分。

当使用命令式映射风格进行映射时,属性字典直接作为properties参数传递给registry.map_imperatively(),它将把它传递给Mapper.properties参数。

其他映射器配置参数

当使用声明性映射风格进行映射时,额外的映射器配置参数通过__mapper_args__类属性配置。有关用法示例,请参阅使用声明性配置选项的映射器。

当使用命令式映射风格进行映射时,关键字参数传递给registry.map_imperatively()方法,该方法将它们传递给Mapper类。

接受的参数的完整范围在Mapper中有文档记录。

映射类行为

在使用registry对象进行所有映射样式时,以下行为是共同的:

默认构造函数

registry将默认构造函数,即__init__方法,应用于所有没有明确自己的__init__方法的映射类。此方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:

代码语言:javascript复制
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"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str]

上面的User类型对象将具有允许创建User对象的构造函数,如下所示:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

提示

声明式数据类映射功能通过使用 Python 数据类提供了一种生成默认__init__()方法的替代方法,并且允许高度可配置的构造函数形式。

警告

类的__init__()方法仅在 Python 代码中构造对象时调用,而不是在从数据库加载或刷新对象时调用。请参阅下一节在加载过程中保持非映射状态,了解如何在加载对象时调用特殊逻辑的入门知识。

包含显式__init__()方法的类将保留该方法,并且不会应用默认构造函数。

要更改所使用的默认构造函数,可以向registry.constructor参数提供用户定义的 Python 可调用对象,该对象将用作默认构造函数。

构造函数也适用于命令式映射:

代码语言:javascript复制
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

如上所述,通过命令式映射描述的类也将具有与registry相关联的默认构造函数。

新版 1.4 中:经典映射现在在通过registry.map_imperatively()方法进行映射时支持标准配置级别的构造函数。### 在加载过程中保持非映射状态

映射类的__init__()方法在 Python 代码中直接构造对象时被调用:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

然而,当使用 ORM Session加载对象时,不会调用__init__()方法:

代码语言:javascript复制
u1 = session.scalars(select(User).where(User.name == "some name")).first()

这是因为从数据库加载时,用于构造对象的操作(在上面的示例中为User)更类似于反序列化,如取消持久性,而不是初始构造。大多数对象的重要状态不是首次组装,而是从数据库行重新加载。

因此,为了在对象中维护不是数据库中存储的数据的状态,使得当对象被加载和构造时此状态存在,下面详细介绍了两种一般方法。

使用 Python 描述符(如 @property),而不是状态,根据需要动态计算属性。

对于简单的属性,这是最简单且最不容易出错的方法。例如,如果一个名为 Point 的对象希望具有这些属性的总和:

代码语言:javascript复制
class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    @property
    def x_plus_y(self):
        return self.x   self.y

使用动态描述符的优势在于,值每次都会重新计算,这意味着它会随着基础属性(在本例中为 xy)可能会发生变化而保持正确的值。

上述模式的其他形式包括 Python 标准库的 cached_property 装饰器(它被缓存,而不是每次重新计算),以及 SQLAlchemy 的 hybrid_property 装饰器,它允许属性既可用于 SQL 查询,也可用于 Python 属性。

使用 InstanceEvents.load() 建立加载时的状态,并且可选地使用补充方法 InstanceEvents.refresh()InstanceEvents.refresh_flush()

这些是在从数据库加载对象或在对象过期后刷新时调用的事件钩子。通常只需要 InstanceEvents.load(),因为非映射的本地对象状态不受过期操作的影响。要修改上面的 Point 示例,如下所示:

代码语言:javascript复制
from sqlalchemy import event

class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    def __init__(self, x, y, **kw):
        super().__init__(x=x, y=y, **kw)
        self.x_plus_y = x   y

@event.listens_for(Point, "load")
def receive_load(target, context):
    target.x_plus_y = target.x   target.y

如果还使用刷新事件,事件钩子可以根据需要堆叠在一个可调用对象上,如下所示:

代码语言:javascript复制
@event.listens_for(Point, "load")
@event.listens_for(Point, "refresh")
@event.listens_for(Point, "refresh_flush")
def receive_load(target, context, attrs=None):
    target.x_plus_y = target.x   target.y

在上述情况下,attrs 属性将出现在 refreshrefresh_flush 事件中,并指示正在刷新的属性名称列表。### 映射类、实例和映射器的运行时内省

使用 registry 映射的类还将包含一些对所有映射通用的属性:

__mapper__ 属性将引用与类相关联的 Mapper

代码语言:javascript复制
mapper = User.__mapper__

当对映射类使用 inspect() 函数时,返回的也是此 Mapper

代码语言:javascript复制
from sqlalchemy import inspect

mapper = inspect(User)

__table__ 属性将引用类被映射到的 Table,或者更通用地引用类被映射到的 FromClause 对象:

代码语言:javascript复制
table = User.__table__

当使用 Mapper.local_table 属性时,返回的也是这个 FromClause

代码语言:javascript复制
table = inspect(User).local_table

对于单表继承映射,其中类是没有自己的表的子类,Mapper.local_table 属性以及 .__table__ 属性将为 None。要检索在查询此类时实际选择的“可选择项”,可通过 Mapper.selectable 属性获得:

代码语言:javascript复制
table = inspect(User).selectable
Mapper 对象的检查

如前一节所示,Mapper 对象可从任何映射类获得,而不管方法如何,使用 Runtime Inspection API 系统。使用 inspect() 函数,可以从映射类获取 Mapper

代码语言:javascript复制
>>> from sqlalchemy import inspect
>>> insp = inspect(User)

可用的详细信息包括 Mapper.columns

代码语言:javascript复制
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

这是一个可以以列表格式或通过单个名称查看的命名空间:

代码语言:javascript复制
>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

其他命名空间包括 Mapper.all_orm_descriptors,其中包括所有映射属性以及混合属性、关联代理:

代码语言:javascript复制
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

以及 Mapper.column_attrs

代码语言:javascript复制
>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

另请参阅

Mapper #### 映射实例的检查

inspect() 函数还提供关于映射类的实例的信息。当应用于映射类的实例时,而不是类本身时,返回的对象被称为 InstanceState,它将提供链接,不仅链接到类使用的 Mapper,还提供了一个详细的界面,提供了关于实例内部属性状态的信息,包括它们当前的值以及这与它们的数据库加载值有何关系。

给定从数据库加载的 User 类的实例:

代码语言:javascript复制
>>> u1 = session.scalars(select(User)).first()

inspect() 函数将返回给我们一个 InstanceState 对象:

代码语言:javascript复制
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

通过该对象,我们可以查看诸如 Mapper 等元素:

代码语言:javascript复制
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

对象所附属的 Session(如果有):

代码语言:javascript复制
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

对象的当前持久状态的信息:

代码语言:javascript复制
>>> insp.persistent
True
>>> insp.pending
False

属性状态信息,例如未加载或延迟加载的属性(假设 addresses 是映射类到相关类的 relationship()):

代码语言:javascript复制
>>> insp.unloaded
{'addresses'}

关于当前 Python 中属性的状态信息,例如自上次刷新以来未修改的属性:

代码语言:javascript复制
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

以及自上次刷新以来对属性进行修改的具体历史:

代码语言:javascript复制
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])

另请参阅

InstanceState

InstanceState.attrs

AttributeState ### 默认构造函数

registry 对所有未显式拥有自己 __init__ 方法的映射类应用默认构造函数,即 __init__ 方法。该方法的行为是提供一个方便的关键字构造函数,将接受所有命名属性作为可选关键字参数。例如:

代码语言:javascript复制
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"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str]

上面的 User 类的对象将具有一个允许创建 User 对象的构造函数:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

提示

声明式数据类映射 功能通过使用 Python 数据类提供了一种生成默认 __init__() 方法的替代方式,并允许高度可配置的构造函数形式。

警告

当对象在 Python 代码中构造时才调用类的 __init__() 方法,而不是在从数据库加载或刷新对象时。请参阅下一节在加载时保持非映射状态,了解如何在加载对象时调用特殊逻辑的基本知识。

包含显式 __init__() 方法的类将保持该方法,不会应用默认构造函数。

若要更改使用的默认构造函数,可以提供用户定义的 Python 可调用对象给 registry.constructor 参数,该对象将用作默认构造函数。

构造函数也适用于命令式映射:

代码语言:javascript复制
from sqlalchemy.orm import registry

mapper_registry = registry()

user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

class User:
    pass

mapper_registry.map_imperatively(User, user_table)

如在命令式映射中所述,上述类将还具有与registry相关联的默认构造函数。

新版本 1.4 中:经典映射现在支持通过registry.map_imperatively()方法映射时的标准配置级构造函数。

在加载期间保持非映射状态

当直接在 Python 代码中构造对象时,会调用映射类的__init__()方法:

代码语言:javascript复制
u1 = User(name="some name", fullname="some fullname")

然而,当使用 ORM Session加载对象时,不会调用__init__()方法:

代码语言:javascript复制
u1 = session.scalars(select(User).where(User.name == "some name")).first()

原因在于,当从数据库加载时,用于构造对象的操作,例如上面的User,更类似于反序列化,例如取消选中,而不是初始构造。对象的大部分重要状态不是首次组装的,而是重新从数据库行加载的。

因此,为了在对象加载以及构造时保持对象中不是存储到数据库的数据的状态,以下详细介绍了两种一般方法。

使用 Python 描述符,如@property,而不是状态,根据需要动态计算属性。

对于简单属性,这是最简单且最少错误的方法。例如,如果具有Point.xPoint.y的对象Point希望具有这些属性的和:

代码语言:javascript复制
class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    @property
    def x_plus_y(self):
        return self.x   self.y

使用动态描述符的优点是值每次计算,这意味着它保持正确的值,因为底层属性(在本例中为xy)可能会更改。

上述模式的其他形式包括 Python 标准库cached_property装饰器(它是缓存的,不会每次重新计算),以及 SQLAlchemy 的hybrid_property装饰器,允许属性同时适用于 SQL 查询。

使用InstanceEvents.load()来在加载时建立状态,可选地使用补充方法InstanceEvents.refresh()InstanceEvents.refresh_flush()

这些是在对象从数据库加载时或在过期后刷新时调用的事件钩子。通常只需要 InstanceEvents.load(),因为非映射的本地对象状态不受到过期操作的影响。要修改上面的 Point 示例,看起来像这样:

代码语言:javascript复制
from sqlalchemy import event

class Point(Base):
    __tablename__ = "point"
    id: Mapped[int] = mapped_column(primary_key=True)
    x: Mapped[int]
    y: Mapped[int]

    def __init__(self, x, y, **kw):
        super().__init__(x=x, y=y, **kw)
        self.x_plus_y = x   y

@event.listens_for(Point, "load")
def receive_load(target, context):
    target.x_plus_y = target.x   target.y

如果需要同时使用刷新事件,事件钩子可以叠加在一个可调用对象上,如下所示:

代码语言:javascript复制
@event.listens_for(Point, "load")
@event.listens_for(Point, "refresh")
@event.listens_for(Point, "refresh_flush")
def receive_load(target, context, attrs=None):
    target.x_plus_y = target.x   target.y

在上面的示例中,attrs 属性将出现在 refreshrefresh_flush 事件中,并指示正在刷新的属性名称列表。

映射类、实例和映射器的运行时内省

使用 registry 进行映射的类还将具有一些所有映射的共同属性:

__mapper__ 属性将引用与该类关联的 Mapper

代码语言:javascript复制
mapper = User.__mapper__

当使用 inspect() 函数对映射类进行检查时,也将返回此 Mapper

代码语言:javascript复制
from sqlalchemy import inspect

mapper = inspect(User)

__table__ 属性将引用将类映射到的 Table,或更一般地,引用 FromClause 对象:

代码语言:javascript复制
table = User.__table__

当使用 Mapper.local_table 属性时,此 FromClause 也将返回:

代码语言:javascript复制
table = inspect(User).local_table

对于单表继承映射,其中类是没有自己的表的子类,Mapper.local_table 属性以及 .__table__ 属性都将为 None。要检索在查询此类时实际选择的“可选项”,可以通过 Mapper.selectable 属性获取:

代码语言:javascript复制
table = inspect(User).selectable
映射器对象的检查

如前一节所示,Mapper 对象可从任何映射类中使用 运行时内省 API 系统获取。使用 inspect() 函数,可以从映射类中获取 Mapper

代码语言:javascript复制
>>> from sqlalchemy import inspect
>>> insp = inspect(User)

可用的详细信息包括 Mapper.columns:

代码语言:javascript复制
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

这是一个可以以列表格式或通过单个名称查看的命名空间:

代码语言:javascript复制
>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

其他命名空间包括 Mapper.all_orm_descriptors,其中包括所有映射属性以及混合体,关联代理:

代码语言:javascript复制
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

以及 Mapper.column_attrs:

代码语言:javascript复制
>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

另请参阅

Mapper #### 映射实例的检查

inspect() 函数还提供了关于映射类的实例的信息。当应用于映射类的实例而不是类本身时,返回的对象被称为 InstanceState,它将提供链接到不仅由该类使用的 Mapper,还提供了有关实例内部属性状态的详细接口的信息,包括它们的当前值以及这与它们的数据库加载值的关系。

给定从数据库加载的 User 类的实例:

代码语言:javascript复制
>>> u1 = session.scalars(select(User)).first()

inspect() 函数将返回一个 InstanceState 对象:

代码语言:javascript复制
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

使用此对象,我们可以查看诸如 Mapper 之类的元素:

代码语言:javascript复制
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

对象所附加到的 Session(如果有):

代码语言:javascript复制
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

关于对象当前的持久性状态的信息:

代码语言:javascript复制
>>> insp.persistent
True
>>> insp.pending
False

属性状态信息,例如尚未加载或延迟加载的属性(假设 addresses 指的是映射类上的 relationship() 到相关类):

代码语言:javascript复制
>>> insp.unloaded
{'addresses'}

有关属性的当前 Python 内部状态的信息,例如自上次刷新以来未被修改的属性:

代码语言:javascript复制
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

以及自上次刷新以来对属性进行的修改的特定历史记录:

代码语言:javascript复制
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])

另请参阅

InstanceState

InstanceState.attrs

AttributeState #### 映射对象的检查

如前一节所示,Mapper对象可以从任何映射类中使用,无论方法如何,都可以使用 Runtime Inspection API 系统。使用inspect()函数,可以从映射类获取Mapper

代码语言:javascript复制
>>> from sqlalchemy import inspect
>>> insp = inspect(User)

可用的详细信息包括Mapper.columns

代码语言:javascript复制
>>> insp.columns
<sqlalchemy.util._collections.OrderedProperties object at 0x102f407f8>

这是一个可以以列表格式或通过单个名称查看的命名空间:

代码语言:javascript复制
>>> list(insp.columns)
[Column('id', Integer(), table=<user>, primary_key=True, nullable=False), Column('name', String(length=50), table=<user>), Column('fullname', String(length=50), table=<user>), Column('nickname', String(length=50), table=<user>)]
>>> insp.columns.name
Column('name', String(length=50), table=<user>)

其他命名空间包括Mapper.all_orm_descriptors,其中包括所有映射属性以及混合属性,关联代理:

代码语言:javascript复制
>>> insp.all_orm_descriptors
<sqlalchemy.util._collections.ImmutableProperties object at 0x1040e2c68>
>>> insp.all_orm_descriptors.keys()
['fullname', 'nickname', 'name', 'id']

以及Mapper.column_attrs

代码语言:javascript复制
>>> list(insp.column_attrs)
[<ColumnProperty at 0x10403fde0; id>, <ColumnProperty at 0x10403fce8; name>, <ColumnProperty at 0x1040e9050; fullname>, <ColumnProperty at 0x1040e9148; nickname>]
>>> insp.column_attrs.name
<ColumnProperty at 0x10403fce8; name>
>>> insp.column_attrs.name.expression
Column('name', String(length=50), table=<user>)

另请参阅

Mapper

映射实例的检查

inspect()函数还提供关于映射类的实例的信息。当应用于映射类的实例而不是类本身时,返回的对象称为InstanceState,它将提供指向类使用的Mapper的链接,以及提供有关实例内部属性状态的详细接口,包括它们当前的值以及这与它们的数据库加载值的关系。

给定从数据库加载的User类的实例:

代码语言:javascript复制
>>> u1 = session.scalars(select(User)).first()

inspect()函数将向我们返回一个InstanceState对象:

代码语言:javascript复制
>>> insp = inspect(u1)
>>> insp
<sqlalchemy.orm.state.InstanceState object at 0x7f07e5fec2e0>

使用此对象,我们可以查看诸如Mapper之类的元素:

代码语言:javascript复制
>>> insp.mapper
<Mapper at 0x7f07e614ef50; User>

对象附加到的Session(如果有):

代码语言:javascript复制
>>> insp.session
<sqlalchemy.orm.session.Session object at 0x7f07e614f160>

对象的当前 persistence state 的信息:

代码语言:javascript复制
>>> insp.persistent
True
>>> insp.pending
False

属性状态信息,如未加载或延迟加载的属性(假设addresses指的是映射类上与相关类的relationship()):

代码语言:javascript复制
>>> insp.unloaded
{'addresses'}

关于属性的当前 Python 状态的信息,例如自上次刷新以来未被修改的属性:

代码语言:javascript复制
>>> insp.unmodified
{'nickname', 'name', 'fullname', 'id'}

以及自上次刷新以来属性修改的具体历史:

代码语言:javascript复制
>>> insp.attrs.nickname.value
'nickname'
>>> u1.nickname = "new nickname"
>>> insp.attrs.nickname.history
History(added=['new nickname'], unchanged=(), deleted=['nickname'])

同样参见

InstanceState

InstanceState.attrs

AttributeState

0 人点赞