SQLAlchemy 2.0 文档
与 dataclasses 和 attrs 集成¶
从 SQLAlchemy 2.0 版本开始,支持 “原生 dataclass” 集成,其中,带注解的声明式表 映射可以通过在映射类中添加单个 mixin 或装饰器来转换为 Python dataclass。
版本 2.0 中的新增内容: 将 dataclass 创建与 ORM Declarative 类集成
还提供了一些模式,可以将现有 dataclasses 映射,以及映射由 attrs 第三方集成库进行检测的类。
声明式 Dataclass 映射¶
SQLAlchemy 带注解的声明式表 映射可以通过额外的 mixin 类或装饰器指令进行增强,这将为映射过程添加额外的步骤,在映射完成后,将就地将映射类转换为 Python dataclass,然后完成映射过程,该过程将 ORM 特定的 检测 应用于该类。这带来的最显著的行为添加是生成一个 __init__()
方法,该方法对带或不带默认值的 positional 和 keyword 参数进行细粒度控制,以及生成 __repr__()
和 __eq__()
等方法。
从 PEP 484 类型化的角度来看,该类被识别为具有 Dataclass 特定的行为,最值得注意的是利用了 PEP 681 “Dataclass 变换”,这允许类型化工具将该类视为使用 @dataclasses.dataclass
装饰器显式装饰的类。
注意
截至2023 年 4 月 4 日,类型化工具对 PEP 681 的支持有限,目前已知 Pyright 和 Mypy (截至1.2 版本) 支持。请注意,Mypy 1.1.1 引入了 PEP 681 支持,但并未正确处理 Python 描述符,这会导致在使用 SQLAlchemy 的 ORM 映射方案时出现错误。
另请参见
https://peps.pythonlang.cn/pep-0681/#the-dataclass-transform-decorator - 关于 SQLAlchemy 等库如何实现 PEP 681 支持的背景信息
可以通过向任何 Declarative 类添加 MappedAsDataclass
mixin (用于 DeclarativeBase
类层次结构) 或使用 registry.mapped_as_dataclass()
类装饰器 (用于装饰器映射) 来添加 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 基类的类
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]
类级特性配置¶
对 dataclasses 特性的支持是部分的。目前支持 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 原生 dataclasses 与普通 dataclasses 的区别在于,要映射的属性在所有情况下都使用 Mapped
通用注解容器进行描述。映射遵循与 带 mapped_column() 的声明式表 中记录的相同形式,并支持 mapped_column()
和 Mapped
的所有特性。
此外,ORM 属性配置构造 (包括 mapped_column()
、relationship()
和 composite()
) 支持每个属性的字段选项,包括 init
、default
、default_factory
和 repr
。这些参数的名称是固定的,如 PEP 681 中所指定。功能等效于 dataclasses
init
(如mapped_column.init
、relationship.init
),如果为 False,则表示该字段不应作为__init__()
方法的一部分。default
(如mapped_column.default
、relationship.default
) 表示该字段的默认值,作为__init__()
方法中的 keyword 参数给出。default_factory
,就像在mapped_column.default_factory
,relationship.default_factory
中,表示一个可调用函数,如果未显式传递给__init__()
方法,该函数将被调用以生成参数的新默认值。repr
默认情况下为 True,表示该字段应是生成的__repr__()
方法的一部分。
与 dataclasses 的另一个主要区别是,属性的默认值 **必须** 使用 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.default
参数(Column
结构)的名称重叠,mapped_column()
结构通过添加一个新的参数 mapped_column.insert_default
来区分这两个名称,该参数将直接填充到 Column.default
参数(Column
)中,独立于 mapped_column.default
上可能设置的内容,mapped_column.default
始终用于 dataclasses 配置。例如,要配置一个 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
)
使用上面的映射,对一个新的 User
对象进行 INSERT
操作,其中没有为 created_at
传递参数,操作过程如下。
>>> 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()
结构以供重复使用。此功能受 dataclasses 功能支持。但是,此功能的一个方面在使用类型工具时需要变通方法,即 PEP 681 特定参数 init
、default
、repr
和 default_factory
**必须** 位于右侧,打包到显式的 mapped_column()
结构中,以便类型工具能够正确解释该属性。例如,下面的方法在运行时可以正常工作,但是类型工具将认为 User()
构造无效,因为它们看不到 init=False
参数存在。
from typing import Annotated
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
# typing tools 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: 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()
层次结构中使用非 dataclass 的 mixin 和抽象基类已弃用,因为这些字段不受 PEP 681 支持,它们不属于 dataclass。对于这种情况,将发出一个警告,稍后该警告将成为错误。
另请参见
将 <cls> 转换为 dataclass 时,属性来自超类 <cls>,该类不是 dataclass。 - 关于基本原理的背景信息
关系配置¶
Mapped
注释与 relationship()
结合使用,与 基本关系模式 中描述的方式相同。当将基于集合的 relationship()
指定为可选的关键字参数时,必须传递 relationship.default_factory
参数,并且它必须引用要使用的集合类。多对一和标量对象引用可以使用 relationship.default
,如果默认值应为 None
。
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()
对象时为 Parent.children
生成一个空列表,而无需传递 children
,类似地,在构造一个新的 Child()
对象时为 Child.parent
生成一个 None
值,而无需传递 parent
。
虽然 relationship.default_factory
可以从 relationship()
给定的集合类自动推断,但这会破坏与 dataclasses 的兼容性,因为 relationship.default_factory
或 relationship.default
的存在决定了参数在渲染到 __init__()
方法时是必需还是可选。
使用未映射的 Dataclass 字段¶
当使用声明式 dataclasses 时,类上也可以使用未映射的字段,这些字段将成为 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")
一个更真实的例子可能是结合使用 Dataclasses 的 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 类型映射继续工作。
与 Pydantic 等其他 Dataclass 提供商集成¶
警告
Pydantic 的 dataclass 层与 SQLAlchemy 的类检测不完全兼容,需要额外的内部更改,许多功能,例如相关集合,可能无法正常工作。
为了与 Pydantic 兼容,请考虑使用 SQLModel ORM,它是在 SQLAlchemy ORM 之上使用 Pydantic 构建的,其中包含明确解决这些不兼容性的特殊实现细节。
SQLAlchemy 的 MappedAsDataclass
类和 registry.mapped_as_dataclass()
方法调用直接进入 Python 标准库的 dataclasses.dataclass
类装饰器,在声明式映射过程应用于类之后。此函数调用可以被替换为其他 Dataclasses 提供商,例如 Pydantic 的提供商,使用 dataclass_callable
参数,该参数被 MappedAsDataclass
作为类关键字参数以及 registry.mapped_as_dataclass()
接受。
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 系列中新引入的 声明式 Dataclass 映射 功能所取代。此功能的较新版本构建在版本 1.4 中首次添加的 dataclass 支持的基础上,本节将对此进行描述。
要映射现有的 dataclass,SQLAlchemy 的“内联”声明式指令不能直接使用;ORM 指令使用三种技术之一分配。
使用“带命令式表的声明式”,要映射的表/列使用分配给类
__table__
属性的Table
对象进行定义;关系在__mapper_args__
字典中定义。使用registry.mapped()
装饰器来映射类。下面的 使用带命令式表的声明式映射预先存在的 dataclasses 中提供了一个示例。使用完整的“声明式”,将声明式解释的指令,例如
Column
、relationship()
添加到dataclasses.field()
结构的.metadata
字典中,在那里它们会被声明式过程消耗。再次使用registry.mapped()
装饰器来映射类。请参阅下面的 使用声明式风格的字段映射预先存在的 dataclasses 中的示例。可以使用
registry.map_imperatively()
方法将“命令式”映射应用于现有的 dataclass,以生成与 命令式映射 中描述的方式完全相同的映射。这在下面的 使用命令式映射映射预先存在的 dataclasses 中进行了说明。
SQLAlchemy 将映射应用于数据类的通用过程与普通类相同,但它还会检测数据类声明过程中包含的类级属性,并在运行时将它们替换为常用的 SQLAlchemy ORM 映射属性。由数据类生成的 __init__
方法保持不变,数据类生成的其它方法,如 __eq__()
、__repr__()
等,也保持不变。
使用声明式与命令式表映射预先存在的 dataclasses¶
以下示例使用 @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
。在使用 声明式数据类映射 时,此调整会自动完成。
使用声明式样式字段映射预先存在的 dataclasses¶
传统功能
这种使用数据类的声明式映射方法应被视为遗留方法。它将继续得到支持,但不太可能比 声明式数据类映射 中介绍的新方法更具优势。
请注意,**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))})
在预先存在的 dataclasses 中使用声明式 Mixin¶
在 使用 Mixin 组合映射层次结构 部分中,介绍了声明式 Mixin 类。声明式 Mixin 的一项要求是,必须使用 declared_attr
装饰器以可调用对象的形式提供某些无法轻松复制的结构,例如 Mixin 中的关系 中的示例。
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")
这种形式在数据类 field()
对象中得到支持,通过使用 lambda 在 field()
中指示 SQLAlchemy 结构。使用 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
对象,用于在“使用声明式表的 dataclasses”风格的映射中使用。
使用命令式映射映射预先存在的 dataclasses¶
如前所述,使用 @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)
使用这种映射风格时,适用于 使用声明式与命令式表映射预先存在的 dataclasses 中提到的相同警告。
将 ORM 映射应用于现有的 attrs 类¶
attrs 库是一个流行的第三方库,它提供了与数据类类似的功能,并且提供了普通数据类中未提供的许多附加功能。
使用 attrs 增强过的类使用 @define
装饰器。该装饰器启动一个过程来扫描类以查找定义类行为的属性,然后使用这些属性生成方法、文档和注释。
SQLAlchemy ORM 支持使用 **声明式与命令式表** 或 **命令式** 映射来映射 attrs 类。这两种风格的通用形式与使用数据类的 使用声明式样式字段映射预先存在的 dataclasses 和 使用声明式与命令式表映射预先存在的 dataclasses 映射形式完全相同,其中数据类或 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__
用于状态存储。当此选项存在时,行为未定义。
使用命令式映射映射属性¶
就像使用数据类一样,我们可以使用 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é! 火龙和 炼金术士 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Fri 08 Nov 2024 08:41:19 AM EST