SQLAlchemy 2.0 文档
与 dataclasses 和 attrs 集成¶
SQLAlchemy 2.0 版本开始引入了“原生 dataclass”集成,其中 Annotated Declarative Table 映射可以通过向映射类添加单个 mixin 或装饰器转换为 Python dataclass。
2.0 版本新增: 将 dataclass 创建与 ORM Declarative 类集成
还有一些可用的模式允许映射现有 dataclass,以及映射由 attrs 第三方集成库检测的类。
Declarative Dataclass 映射¶
SQLAlchemy Annotated Declarative Table 映射可以通过额外的 mixin 类或装饰器指令进行增强。这将在映射完成后向 Declarative 过程添加一个额外的步骤,该步骤会将映射类就地转换为 Python dataclass,然后再完成映射过程,该过程将 ORM 特定的 instrumentation 应用于该类。 这提供的最突出的行为添加是生成一个 __init__()
方法,该方法可以精细地控制带或不带默认值的位置参数和关键字参数,以及生成诸如 __repr__()
和 __eq__()
之类的方法。
从 PEP 484 类型注解的角度来看,该类被识别为具有 Dataclass 特定的行为,最显著的是利用了 PEP 681 “Dataclass Transforms”,这允许类型检查工具将该类视为好像它已使用 @dataclasses.dataclass
装饰器显式装饰一样。
注意
截至 2023 年 4 月 4 日,类型检查工具对 PEP 681 的支持有限,目前已知 Pyright 以及 1.2 版本的 Mypy 支持它。 请注意,Mypy 1.1.1 引入了 PEP 681 支持,但未正确处理 Python 描述符,这将在使用 SQLAlchemy 的 ORM 映射方案时导致错误。
另请参阅
https://peps.pythonlang.cn/pep-0681/#the-dataclass-transform-decorator - 关于 SQLAlchemy 等库如何启用 PEP 681 支持的背景信息
可以通过以下方式将 Dataclass 转换添加到任何 Declarative 类:将 MappedAsDataclass
mixin 添加到 DeclarativeBase
类层次结构,或者对于装饰器映射,使用 registry.mapped_as_dataclass()
类装饰器。
MappedAsDataclass
mixin 可以应用于 Declarative Base
类或任何超类,如下例所示
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
class Base(MappedAsDataclass, DeclarativeBase):
"""subclasses will be converted to dataclasses"""
class User(Base):
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
或者可以直接应用于从 Declarative base 扩展的类
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
class Base(DeclarativeBase):
pass
class User(MappedAsDataclass, Base):
"""User class will be converted to a dataclass"""
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
当使用装饰器形式时,仅支持 registry.mapped_as_dataclass()
装饰器
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
类级别特性配置¶
对 dataclass 特性的支持是部分的。目前支持 init
、repr
、eq
、order
和 unsafe_hash
特性,match_args
和 kw_only
在 Python 3.10+ 上受支持。 目前不支持 frozen
和 slots
特性。
当使用带有 MappedAsDataclass
的 mixin 类形式时,类配置参数作为类级别参数传递
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
class Base(DeclarativeBase):
pass
class User(MappedAsDataclass, Base, repr=False, unsafe_hash=True):
"""User class will be converted to a dataclass"""
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
当使用带有 registry.mapped_as_dataclass()
的装饰器形式时,类配置参数直接传递给装饰器
from sqlalchemy.orm import registry
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
reg = registry()
@reg.mapped_as_dataclass(unsafe_hash=True)
class User:
"""User class will be converted to a dataclass"""
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
有关 dataclass 类选项的背景信息,请参阅 dataclasses 文档,网址为 @dataclasses.dataclass。
属性配置¶
SQLAlchemy 原生 dataclass 与普通 dataclass 的不同之处在于,要映射的属性在所有情况下都使用 Mapped
通用注解容器进行描述。 映射遵循与 带有 mapped_column() 的 Declarative Table 中记录的相同形式,并且支持 mapped_column()
和 Mapped
的所有特性。
此外,ORM 属性配置构造(包括 mapped_column()
、relationship()
和 composite()
)支持每个属性的字段选项,包括 init
、default
、default_factory
和 repr
。 这些参数的名称是固定的,如 PEP 681 中所指定。 功能与 dataclass 等效
init
,如mapped_column.init
、relationship.init
,如果为 False,则表示该字段不应成为__init__()
方法的一部分default
,如mapped_column.default
、relationship.default
,表示该字段的默认值,作为__init__()
方法中的关键字参数给出。default_factory
,如mapped_column.default_factory
、relationship.default_factory
,表示一个可调用函数,如果未显式传递给__init__()
方法,则将调用该函数来生成参数的新默认值。repr
默认为 True,表示该字段应成为生成的__repr__()
方法的一部分
与 dataclass 的另一个主要区别是,属性的默认值必须使用 ORM 构造的 default
参数进行配置,例如 mapped_column(default=None)
。 不支持类似于 dataclass 语法的语法,该语法接受简单的 Python 值作为默认值,而无需使用 @dataclases.field()
。
以使用 mapped_column()
为例,下面的映射将生成一个 __init__()
方法,该方法仅接受字段 name
和 fullname
,其中 name
是必需的,可以按位置传递,而 fullname
是可选的。 我们期望由数据库生成的 id
字段根本不是构造函数的一部分
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
fullname: Mapped[str] = mapped_column(default=None)
# 'fullname' is optional keyword argument
u1 = User("name")
列默认值¶
为了解决 default
参数与 Column
构造的现有 Column.default
参数的名称重叠问题,mapped_column()
构造通过添加一个新参数 mapped_column.insert_default
来消除歧义,该参数将直接填充到 Column
的 Column.default
参数中,这与 mapped_column.default
上可能设置的内容无关,后者始终用于 dataclass 配置。 例如,要配置一个 datetime 列,其 Column.default
设置为 func.utc_timestamp()
SQL 函数,但该参数在构造函数中是可选的
from datetime import datetime
from sqlalchemy import func
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
created_at: Mapped[datetime] = mapped_column(
insert_default=func.utc_timestamp(), default=None
)
使用上述映射,对于没有传递 created_at
参数的新 User
对象的 INSERT
操作将按如下方式进行
>>> with Session(e) as session:
... session.add(User())
... session.commit()
BEGIN (implicit)
INSERT INTO user_account (created_at) VALUES (utc_timestamp())
[generated in 0.00010s] ()
COMMIT
与 Annotated 集成¶
将完整的列声明映射到 Python 类型 中介绍的方法说明了如何使用 PEP 593 Annotated
对象来打包完整的 mapped_column()
构造以供重用。 虽然 Annotated
对象可以与 dataclass 结合使用,但遗憾的是,dataclass 特定的关键字参数不能在 Annotated 构造中使用。 这包括 PEP 681 特定的参数 init
、default
、repr
和 default_factory
,这些参数必须存在于 mapped_column()
或与类属性内联的类似构造中。
在版本 2.0.14/2.0.22 中更改: 当 Annotated
构造与 ORM 构造(如 mapped_column()
)一起使用时,无法容纳 dataclass 字段参数,例如 init
和 repr
- 这种用法违背了 Python dataclass 的设计,并且不受 PEP 681 的支持,因此也被 SQLAlchemy ORM 在运行时拒绝。 现在发出弃用警告,并且该属性将被忽略。
例如,下面的 init=False
参数将被忽略,并且还会发出弃用警告
from typing import Annotated
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
# typing tools as well as SQLAlchemy will ignore init=False here
intpk = Annotated[int, mapped_column(init=False, primary_key=True)]
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
id: Mapped[intpk]
# typing error as well as runtime error: Argument missing for parameter "id"
u1 = User()
相反,mapped_column()
也必须出现在右侧,并显式设置为 mapped_column.init
; 其他参数可以保留在 Annotated
构造中
from typing import Annotated
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
intpk = Annotated[int, mapped_column(primary_key=True)]
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
# init=False and other pep-681 arguments must be inline
id: Mapped[intpk] = mapped_column(init=False)
u1 = User()
使用 mixin 和抽象超类¶
在 MappedAsDataclass
映射类中使用的任何 mixin 或基类,如果包含 Mapped
属性,则它们本身必须是 MappedAsDataclass
层次结构的一部分,如下面的示例所示,该示例使用了 mixin
class Mixin(MappedAsDataclass):
create_user: Mapped[int] = mapped_column()
update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)
class Base(DeclarativeBase, MappedAsDataclass):
pass
class User(Base, Mixin):
__tablename__ = "sys_user"
uid: Mapped[str] = mapped_column(
String(50), init=False, default_factory=uuid4, primary_key=True
)
username: Mapped[str] = mapped_column()
email: Mapped[str] = mapped_column()
支持 PEP 681 的 Python 类型检查器否则不会将来自非 dataclass mixin 的属性视为 dataclass 的一部分。
自版本 2.0.8 起已弃用: 在 MappedAsDataclass
或 registry.mapped_as_dataclass()
层次结构中使用 mixin 和抽象基类(它们本身不是 dataclass)已弃用,因为这些字段不受 PEP 681 支持,不属于 dataclass。 对于这种情况,会发出警告,稍后将变为错误。
另请参阅
当将 <cls> 转换为 dataclass 时,属性源自不是 dataclass 的超类 <cls>。 - 关于原理的背景信息
关系配置¶
Mapped
注解与 relationship()
结合使用的方式与 基本关系模式 中描述的方式相同。 当将基于集合的 relationship()
指定为可选关键字参数时,必须传递 relationship.default_factory
参数,并且它必须引用要使用的集合类。 如果默认值是 None
,则多对一和标量对象引用可以使用 relationship.default
from typing import List
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
reg = registry()
@reg.mapped_as_dataclass
class Parent:
__tablename__ = "parent"
id: Mapped[int] = mapped_column(primary_key=True)
children: Mapped[List["Child"]] = relationship(
default_factory=list, back_populates="parent"
)
@reg.mapped_as_dataclass
class Child:
__tablename__ = "child"
id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id"))
parent: Mapped["Parent"] = relationship(default=None)
当构造新的 Parent()
对象而不传递 children
时,上面的映射将为 Parent.children
生成一个空列表; 类似地,当构造新的 Child()
对象而不传递 parent
时,将为 Child.parent
生成一个 None
值。
虽然 relationship.default_factory
可以从 relationship()
本身的给定集合类中自动派生,但这会破坏与 dataclass 的兼容性,因为 relationship.default_factory
或 relationship.default
的存在决定了当呈现到 __init__()
方法中时,参数是必需的还是可选的。
使用非映射 Dataclass 字段¶
当使用 Declarative dataclass 时,也可以在该类上使用非映射字段,这些字段将成为 dataclass 构造过程的一部分,但不会被映射。 任何不使用 Mapped
的字段都将被映射过程忽略。 在下面的示例中,字段 ctrl_one
和 ctrl_two
将成为对象的实例级状态的一部分,但不会被 ORM 持久化
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped_as_dataclass
class Data:
__tablename__ = "data"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
status: Mapped[str]
ctrl_one: Optional[str] = None
ctrl_two: Optional[str] = None
上面的 Data
实例可以创建为
d1 = Data(status="s1", ctrl_one="ctrl1", ctrl_two="ctrl2")
一个更真实的示例可能是结合使用 Dataclass 的 InitVar
特性和 __post_init__()
特性来接收仅用于初始化的字段,这些字段可用于组合持久化数据。 在下面的示例中,User
类声明使用 id
、name
和 password_hash
作为映射特性,但利用仅用于初始化的 password
和 repeat_password
字段来表示用户创建过程 (注意:要运行此示例,请将函数 your_crypt_function_here()
替换为第三方加密函数,例如 bcrypt 或 argon2-cffi)
from dataclasses import InitVar
from typing import Optional
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
reg = registry()
@reg.mapped_as_dataclass
class User:
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(init=False, primary_key=True)
name: Mapped[str]
password: InitVar[str]
repeat_password: InitVar[str]
password_hash: Mapped[str] = mapped_column(init=False, nullable=False)
def __post_init__(self, password: str, repeat_password: str):
if password != repeat_password:
raise ValueError("passwords do not match")
self.password_hash = your_crypt_function_here(password)
上面的对象是使用参数 password
和 repeat_password
创建的,这些参数被预先使用,以便可以生成 password_hash
变量
>>> u1 = User(name="some_user", password="xyz", repeat_password="xyz")
>>> u1.password_hash
'$6$9ppc... (example crypted string....)'
在版本 2.0.0rc1 中更改: 当使用 registry.mapped_as_dataclass()
或 MappedAsDataclass
时,可以包含不包含 Mapped
注解的字段,这些字段将被视为生成的 dataclass 的一部分,但不会被映射,而无需同时指示 __allow_unmapped__
类属性。 之前的 2.0 beta 版本将要求此属性显式存在,即使此属性的目的只是允许旧式 ORM 类型映射继续运行。
与备选 Dataclass 提供程序(如 Pydantic)集成¶
警告
Pydantic 的 dataclass 层与 SQLAlchemy 的类检测并非完全兼容,除非进行额外的内部更改,并且许多功能(例如相关集合)可能无法正常工作。
为了实现 Pydantic 兼容性,请考虑 SQLModel ORM,它是在 SQLAlchemy ORM 之上使用 Pydantic 构建的,其中包括显式解决这些不兼容性的特殊实现细节。
在将声明式映射过程应用于类之后,SQLAlchemy 的 MappedAsDataclass
类和 registry.mapped_as_dataclass()
方法直接调用 Python 标准库 dataclasses.dataclass
类装饰器。 可以使用 MappedAsDataclass
作为类关键字参数以及 registry.mapped_as_dataclass()
接受的 dataclass_callable
参数,将此函数调用交换为备选 dataclass 提供程序,例如 Pydantic 的提供程序。
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
from sqlalchemy.orm import registry
class Base(
MappedAsDataclass,
DeclarativeBase,
dataclass_callable=pydantic.dataclasses.dataclass,
):
pass
class User(Base):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
上面的 User
类将作为 dataclass 应用,使用 Pydantic 的 pydantic.dataclasses.dataclasses
可调用对象。 此过程既适用于映射类,也适用于从 MappedAsDataclass
扩展或直接应用了 registry.mapped_as_dataclass()
的 mixin。
2.0.4 版本新增: 为 MappedAsDataclass
和 registry.mapped_as_dataclass()
添加了 dataclass_callable
类和方法参数,并调整了一些 dataclass 内部机制,以适应更严格的 dataclass 函数,例如 Pydantic 的函数。
将 ORM 映射应用于现有 dataclass (旧式 dataclass 用法)¶
旧式特性
此处描述的方法已被 SQLAlchemy 2.0 系列中新增的 Declarative Dataclass 映射 特性取代。 此特性的较新版本建立在 1.4 版本中首次添加的 dataclass 支持之上,本节将对此进行描述。
要映射现有 dataclass,SQLAlchemy 的“内联”声明式指令不能直接使用; ORM 指令使用以下三种技术之一分配
使用“Declarative with Imperative Table”,要映射的表/列是使用分配给类的
__table__
属性的Table
对象定义的; 关系在__mapper_args__
字典中定义。 该类是使用registry.mapped()
装饰器映射的。 下面的 使用 Declarative With Imperative Table 映射预先存在的 dataclass 中有一个示例。使用完整的“声明式”,声明式解释的指令,例如
Column
,relationship()
被添加到dataclasses.field()
构造的.metadata
字典中,在那里它们被声明式过程使用。该类再次使用registry.mapped()
装饰器进行映射。请参阅下面的示例,位于 使用声明式风格字段映射预先存在的数据类。可以使用
registry.map_imperatively()
方法将“命令式”映射应用于现有的数据类,以完全按照 命令式映射 中描述的方式生成映射。这在下面的 使用命令式映射映射预先存在的数据类 中进行了说明。
SQLAlchemy 将映射应用于数据类的通用过程与应用于普通类的过程相同,但也包括 SQLAlchemy 将检测作为数据类声明过程一部分的类级别属性,并在运行时将其替换为通常的 SQLAlchemy ORM 映射属性。由数据类生成的 __init__
方法保持不变,数据类生成的其他所有方法(例如 __eq__()
, __repr__()
等)也是如此。
使用声明式与命令式表映射预先存在的数据类¶
下面是使用 @dataclass
和 声明式与命令式表(又名混合声明式) 进行映射的示例。显式构造一个完整的 Table
对象并将其分配给 __table__
属性。实例字段使用普通数据类语法定义。其他 MapperProperty
定义(例如 relationship()
)放置在 __mapper_args__ 类级别字典中 properties
键下,对应于 Mapper.properties
参数
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional
from sqlalchemy import Column, ForeignKey, Integer, String, Table
from sqlalchemy.orm import registry, relationship
mapper_registry = registry()
@mapper_registry.mapped
@dataclass
class 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)),
)
id: int = field(init=False)
name: Optional[str] = None
fullname: Optional[str] = None
nickname: Optional[str] = None
addresses: List[Address] = field(default_factory=list)
__mapper_args__ = { # type: ignore
"properties": {
"addresses": relationship("Address"),
}
}
@mapper_registry.mapped
@dataclass
class Address:
__table__ = Table(
"address",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("user_id", Integer, ForeignKey("user.id")),
Column("email_address", String(50)),
)
id: int = field(init=False)
user_id: int = field(init=False)
email_address: Optional[str] = None
在上面的示例中,User.id
、Address.id
和 Address.user_id
属性被定义为 field(init=False)
。这意味着这些属性的参数不会添加到 __init__()
方法中,但是 Session
仍然能够在从自动递增或其他默认值生成器刷新期间获取它们的值后设置它们。为了允许在构造函数中显式指定它们,它们应该被赋予默认值 None
。
为了单独声明 relationship()
,需要直接在 Mapper.properties
字典中指定它,该字典本身在 __mapper_args__
字典中指定,以便将其传递给 Mapper
的构造函数。 这种方法的替代方案在下一个示例中。
警告
声明数据类 field()
设置 default
以及 init=False
将无法按预期方式与完全普通的数据类一起工作,因为 SQLAlchemy 类检测将替换由数据类创建过程在类上设置的默认值。请改用 default_factory
。当使用 声明式数据类映射 时,会自动完成此适配。
使用声明式风格字段映射预先存在的数据类¶
旧式特性
这种使用数据类进行声明式映射的方法应被视为遗留方法。它将继续受到支持,但不太可能提供任何优于 声明式数据类映射 中详述的新方法的优势。
请注意,此用法不支持 mapped_column();Column
构造应继续用于在 dataclasses.field()
的 metadata
字段中声明表元数据。
完全声明式方法要求 Column
对象被声明为类属性,当使用数据类时,这将与数据类级别的属性冲突。将它们组合在一起的一种方法是利用 dataclass.field
对象上的 metadata
属性,可以在其中提供 SQLAlchemy 特定的映射信息。当类指定属性 __sa_dataclass_metadata_key__
时,声明式支持提取这些参数。 这也提供了一种更简洁的方法来指示 relationship()
关联
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import registry, relationship
mapper_registry = registry()
@mapper_registry.mapped
@dataclass
class User:
__tablename__ = "user"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
name: str = field(default=None, metadata={"sa": Column(String(50))})
fullname: str = field(default=None, metadata={"sa": Column(String(50))})
nickname: str = field(default=None, metadata={"sa": Column(String(12))})
addresses: List[Address] = field(
default_factory=list, metadata={"sa": relationship("Address")}
)
@mapper_registry.mapped
@dataclass
class Address:
__tablename__ = "address"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
user_id: int = field(init=False, metadata={"sa": Column(ForeignKey("user.id"))})
email_address: str = field(default=None, metadata={"sa": Column(String(50))})
将声明式 Mixin 与预先存在的数据类一起使用¶
在 使用 Mixin 组合映射层次结构 部分中,介绍了声明式 Mixin 类。声明式 mixin 的一个要求是,某些不容易复制的构造必须作为可调用对象给出,使用 declared_attr
装饰器,例如在 混合关系 中的示例中
class RefTargetMixin:
@declared_attr
def target_id(cls) -> Mapped[int]:
return mapped_column("target_id", ForeignKey("target.id"))
@declared_attr
def target(cls):
return relationship("Target")
通过使用 lambda 来指示 field()
内的 SQLAlchemy 构造,数据类 field()
对象中支持这种形式。 使用 declared_attr()
包围 lambda 是可选的。 如果我们想生成上面的 User
类,其中 ORM 字段来自 mixin 本身就是一个数据类,则形式将是
@dataclass
class UserMixin:
__tablename__ = "user"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
addresses: List[Address] = field(
default_factory=list, metadata={"sa": lambda: relationship("Address")}
)
@dataclass
class AddressMixin:
__tablename__ = "address"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(init=False, metadata={"sa": Column(Integer, primary_key=True)})
user_id: int = field(
init=False, metadata={"sa": lambda: Column(ForeignKey("user.id"))}
)
email_address: str = field(default=None, metadata={"sa": Column(String(50))})
@mapper_registry.mapped
class User(UserMixin):
pass
@mapper_registry.mapped
class Address(AddressMixin):
pass
1.4.2 版本新增: 增加了对“声明属性”风格 mixin 属性的支持,即 relationship()
构造以及带有外键声明的 Column
对象,用于“带有声明式表的数据类”风格的映射中。
使用命令式映射映射预先存在的数据类¶
如前所述,使用 @dataclass
装饰器设置为数据类的类,然后可以使用 registry.mapped()
装饰器进一步装饰,以便将声明式风格的映射应用于该类。作为使用 registry.mapped()
装饰器的替代方法,我们也可以将类传递给 registry.map_imperatively()
方法,以便我们可以命令式地将所有 Table
和 Mapper
配置传递给函数,而不是将它们作为类变量在类本身上定义
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import field
from typing import List
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
mapper_registry = registry()
@dataclass
class User:
id: int = field(init=False)
name: str = None
fullname: str = None
nickname: str = None
addresses: List[Address] = field(default_factory=list)
@dataclass
class Address:
id: int = field(init=False)
user_id: int = field(init=False)
email_address: str = None
metadata_obj = MetaData()
user = Table(
"user",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
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)
当使用这种映射样式时,使用声明式与命令式表映射预先存在的数据类 中提到的相同警告适用。
将 ORM 映射应用于现有的 attrs 类¶
attrs 库是一个流行的第三方库,它提供与数据类类似的功能,并提供了许多在普通数据类中找不到的附加功能。
使用 attrs 增强的类使用 @define
装饰器。此装饰器启动一个过程,扫描类以查找定义类行为的属性,然后这些属性用于生成方法、文档和注释。
SQLAlchemy ORM 支持使用 声明式与命令式表 或 命令式 映射来映射 attrs 类。这两种风格的通用形式完全等同于用于数据类的 使用声明式风格字段映射预先存在的数据类 和 使用声明式与命令式表映射预先存在的数据类 映射形式,其中数据类或 attrs 使用的内联属性指令保持不变,并且 SQLAlchemy 的面向表的检测在运行时应用。
attrs 的 @define
装饰器默认情况下会将带注释的类替换为新的基于 __slots__ 的类,这是不支持的。当使用旧式注释 @attr.s
或使用 define(slots=False)
时,类不会被替换。此外,attrs 在装饰器运行后会删除其自身的类绑定属性,以便 SQLAlchemy 的映射过程可以接管这些属性而没有任何问题。@attr.s
和 @define(slots=False)
这两个装饰器都适用于 SQLAlchemy。
使用声明式“命令式表”映射 attrs¶
在“声明式与命令式表”风格中,Table
对象与声明式类内联声明。 首先将 @define
装饰器应用于类,然后将 registry.mapped()
装饰器应用于第二个
from __future__ import annotations
from typing import List
from typing import Optional
from attrs import define
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
mapper_registry = registry()
@mapper_registry.mapped
@define(slots=False)
class User:
__table__ = Table(
"user",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("FullName", String(50), key="fullname"),
Column("nickname", String(12)),
)
id: Mapped[int]
name: Mapped[str]
fullname: Mapped[str]
nickname: Mapped[str]
addresses: Mapped[List[Address]]
__mapper_args__ = { # type: ignore
"properties": {
"addresses": relationship("Address"),
}
}
@mapper_registry.mapped
@define(slots=False)
class Address:
__table__ = Table(
"address",
mapper_registry.metadata,
Column("id", Integer, primary_key=True),
Column("user_id", Integer, ForeignKey("user.id")),
Column("email_address", String(50)),
)
id: Mapped[int]
user_id: Mapped[int]
email_address: Mapped[Optional[str]]
注意
attrs
的 slots=True
选项在映射类上启用 __slots__
,除非完全实现替代的 属性检测,否则不能与 SQLAlchemy 映射一起使用,因为映射类通常依赖于直接访问 __dict__
进行状态存储。当此选项存在时,行为未定义。
使用命令式映射映射 attrs¶
正如数据类的情况一样,我们也可以使用 registry.map_imperatively()
来映射现有的 attrs
类
from __future__ import annotations
from typing import List
from attrs import define
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
mapper_registry = registry()
@define(slots=False)
class User:
id: int
name: str
fullname: str
nickname: str
addresses: List[Address]
@define(slots=False)
class Address:
id: int
user_id: int
email_address: Optional[str]
metadata_obj = MetaData()
user = Table(
"user",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("fullname", String(50)),
Column("nickname", String(12)),
)
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)
上述形式等同于前面使用声明式与命令式表的示例。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:2025 年 3 月 11 日星期二下午 02:40:17 EDT