与 dataclasses 和 attrs 集成¶
从 2.0 版开始,SQLAlchemy 具有“原生数据类”集成功能,其中带注释的声明式表
可以通过向 mapped 类添加单个 mixin 或 decorator 来将 mapping 转换为 Python 数据类。
2.0 版本中的新功能: 使用 ORM Declarative 类集成数据类创建
还有一些模式允许现有数据类
mapped,以及由
attrs 第三方集成库。
声明式数据类映射¶
SQLAlchemy 带注释的声明式表
映射可以通过额外的
mixin 类或 decorator 指令,这将在
映射完成后的 Declarative 进程,该进程将
在完成应用 ORM 特定插桩的映射过程之前,将映射的类就地放入 Python 数据类中
到班级。 这提供的最突出的行为添加是
生成 __init__()
方法,对位置和关键字参数进行精细控制,带或不带默认值,以及生成 __repr__()
和 __eq__() 等
方法。
从 PEP 484 类型的角度来看,该类被认为具有特定于 Dataclass 的行为,最明显的是利用 PEP 681
“Dataclass Transforms”,它允许键入工具考虑类
就好像它是使用 @dataclasses.dataclass
显式装饰的一样
装饰。
注意
截至 2023 年 4 月 4 日,键入工具对 PEP 681 的支持是有限的,目前已知 Pyright 和 Mypy 从 1.2 版开始支持。 请注意,Mypy 1.1.1 引入了
PEP 681 支持但未正确容纳 Python 描述符,这将在使用 SQLAlchemy 的 ORM 映射方案时导致错误。
另请参阅
https://peps.python.org/pep-0681/#the-dataclass-transform-decorator - SQLAlchemy 等库如何启用 PEP 681 支持的背景
可以通过添加
MappedAsData类
mixin 添加到 DeclarativeBase
类
层次结构,或者使用
registry.mapped_as_dataclass()
类 decorator 的 Decorator 进行设置。
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]
当使用 decorator 表单时,只有 registry.mapped_as_dataclass()
Decorator 支持:
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
功能。
Python 3.10+ 支持 match_args
和 kw_only
。当前不支持 frozen
和 slots
功能。
当将 mixin 类表单与 MappedAsDataclass
一起使用时,类配置参数将作为类级参数传递:
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.dataclass 上的 dataclasses 文档。
属性配置¶
SQLAlchemy 原生数据类与普通数据类的不同之处在于,要映射的属性是使用 Mapped
泛型注释容器。 映射遵循相同的
形式,如使用 mapped_column() 的 Declarative Table 中记录的那些,并且支持 mapped_column()
和 Mapped
的所有功能。
此外,ORM 属性配置结构包括
mapped_column()、
relationship()
和 composite()
支持每个属性的字段选项,包括 init
、default
、
default_factory
和 repr
.这些参数的名称是固定的,如 PEP 681 中所指定。功能等同于数据类:
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__()
方法,将调用该函数为参数生成新的默认值。雷普
True 表示该字段应是生成的__repr__()
方法
与数据类的另一个主要区别是属性的默认值
必须使用 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.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
)
使用上述映射,未传递 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带注释
的对象将整个打包
mapped_column()
结构进行重用。虽然 Annotated
对象可以与 dataclasses 结合使用,但特定于 dataclass 的关键字
遗憾的是,参数不能在 Annotated 构造中使用。这包括特定于 PEP 681 的参数 init
、default
、repr
和
default_factory
,它必须存在于 mapped_column()
或类似的构造。
在 2.0.14/2.0.22 版本发生变更: 当与 ORM 结构(如 mapped_column()
)一起使用时,Annotated
结构无法容纳数据类字段参数,例如 init
和 repr
- 这种使用违背了 Python 数据类的设计,并且不受 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
属性的 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 类型检查器不会将来自非数据类 mixin 的属性视为数据类的一部分。
2.0.8 版后已移除: 在 中使用 mixin 和 abstract base
MappedAsData类
或
registry.mapped_as_dataclass()
本身不是数据类的层次结构已被弃用,因为 PEP 681 不支持这些字段属于数据类。在这种情况下,将发出警告,该警告稍后将是一个错误。
另请参阅
关系配置¶
Mapped
注解与
relationship()
的使用方式与
基本关系模式。 当指定基于集合的
relationship()
作为可选关键字参数,则
relationship.default_factory
参数,并且它
必须引用要使用的集合类。 Many-to-one 和
标量对象引用可以使用
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()
对象而不传递 children
时,上面的映射将为 Parent.children
生成一个空列表,同样,当构造新的 Child()
对象而不传递 parent
时,将为 Child.parent
生成一个 None
值。
虽然 relationship.default_factory
可以从 relationship()
的给定集合类中自动派生
本身,这会破坏与 Dataclasses 的兼容性,因为
relationship.default_factory
或
relationship.default
用于确定参数在渲染到 __init__()
方法时是必需的还是可选的。
使用非映射数据类字段¶
使用声明式数据类时,也可以在类上使用非映射字段,这将是数据类构造过程的一部分,但不会被映射。任何不使用 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__()
功能结合使用,以接收可用于编写持久化数据的仅 init-only 字段。在下面的示例中,用户
class 使用 id
、name
和 password_hash
作为映射特征进行声明,但使用仅限 init 的 password
和 repeat_password
字段来表示用户创建过程(注意:要运行此示例,请将函数 your_crypt_function_here()
替换为第三方 crypt 函数,例如 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
,这些 Cookie 会预先消耗掉,以便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()
或 MappedAsData类
中,这些字段不包含
可以包含 Mapped
Comments,它将被视为结果数据类的一部分,但不会被映射,而无需同时指示 __allow_unmapped__
类属性。以前的 2.0 beta 版本将要求显式存在此属性,即使此属性的目的只是允许旧版 ORM 类型的映射继续运行。
与替代数据类提供者(如 Pydantic)集成¶
警告
Pydantic 的数据类层与没有额外内部更改的 SQLAlchemy 的类插桩不完全兼容,并且许多功能(例如相关集合)可能无法正常工作。
对于 Pydantic 兼容性,请考虑
SQLModelORM 是在 SQLAlchemy ORM 之上用 Pydantic 构建的,它包括显式解决这些不兼容的特殊实现细节。
在将声明性映射过程应用于类后,SQLAlchemy 的 MappedAsDataclass
类和 registry.mapped_as_dataclass()
方法直接调用到 Python 标准库 dataclasses.dataclass
类装饰器中。此函数调用可以换成备用数据类提供程序,例如 Pydantic 的提供程序,使用 MappedAsDataclass
接受的 dataclass_callable
参数作为类关键字参数以及: 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
类将作为数据类应用,使用 Pydantic 的
pydantic.dataclasses.dataclasses
调用。 该过程可用
既适用于 Map 类,也适用于从
MappedAsData类
或具有
registry.mapped_as_dataclass()
直接应用。
2.0.4 版本中的新功能: 添加了 MappedAsDataclass
和 dataclass_callable
类和方法参数
registry.mapped_as_dataclass()
,并调整了一些数据类内部结构以适应更严格的数据类函数,例如 Pydantic 的数据类函数。
将 ORM 映射应用于现有数据类(使用遗留数据类)¶
旧版功能
此处描述的方法已被 SQLAlchemy 2.0 系列中新增的声明性数据类映射功能所取代。此功能的此较新版本建立在版本 1.4 中首次添加的数据类支持之上,本节将对此进行介绍。
要映射现有数据类,不能直接使用 SQLAlchemy 的 “inline” 声明性指令;ORM 指令使用以下三种技术之一进行分配:
使用 “Declarative with Imperative Table” ,使用分配给__table__
类的属性;关系在__mapper_args__
字典。 该类使用registry.mapped()
装饰器。 下面是一个示例,网址为 使用 Declarative With Imperative Table 映射预先存在的数据类。
使用完整的 “Declarative”,Declarative 解释的指令(例如Column
、relationship()
添加到.metadata
字典中,它们被声明式进程使用。再次使用
registry.mapped()
装饰器映射该类。请参阅下面的示例,请参阅使用声明式字段映射预先存在的数据类。
可以使用registry.map_imperatively()
方法将 “Imperative” 映射应用于现有数据类,以与 Imperative Mapping 中描述的完全相同的方式生成映射。这在下面的 使用命令式映射映射预先存在的数据类 中进行了说明。
SQLAlchemy 将 Map 应用于数据类的一般过程与普通类相同,但也包括 SQLAlchemy 将检测作为数据类声明过程一部分的类级属性,并在运行时将它们替换为通常的 SQLAlchemy ORM 映射属性。__init__
方法
将由 Dataclasses 生成,就像
对于 DataClasses 生成的所有其他方法,例如
__eq__()、
__repr__()
等。
使用 Declarative With Imperative Table 映射预先存在的数据类¶
使用 @dataclass
using 的映射示例
Declarative with Imperative Table(又名 Hybrid Declarative)如下所示。一个完整的
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
attributes 定义为 field(init=False)。
这意味着这些方法的参数不会添加到 __init__()
方法中,而是
Session
在 flush 期间从 autoincrement 或其他 default value generator 获取其值后,仍然可以设置它们。要允许在构造函数中显式指定它们,应为它们提供默认值 None
。
对于要单独声明的 relationship(),
需要直接在 Mapper.properties
字典中指定它,该字典本身在 __mapper_args__
字典中指定,以便将其传递给 Mapper
的构造函数。此方法的替代方法在下一个示例中。
警告
将 dataclass field()
设置与 init=False
一起声明为 default
不会像使用完全普通的 DataClass 那样工作,
因为 SQLAlchemy 类插桩将替换
DataClass 创建过程在类上设置的默认值。
请改用 default_factory
。当使用 Declarative Dataclass Mapping 时,这种调整会自动完成。
使用声明式字段映射预先存在的数据类¶
旧版功能
这种使用数据类进行声明性映射的方法应被视为遗留方法。它将继续受支持,但与 Declarative Dataclass Mapping 中详述的新方法相比,它不太可能提供任何优势。
请注意,此用途不支持 mapped_column();Column
结构应继续用于在 dataclasses.field()
的 metadata
字段中声明表元数据。
完全声明性方法要求将 Column
对象声明为类属性,当使用数据类时,该属性将与数据类级属性冲突。将这些组合在一起的一种方法是使用 dataclass.field
上的 metadata
属性
对象,其中可以提供特定于 SQLAlchemy 的映射信息。
Declarative 支持在类
指定 __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
装饰器,例如 Mixing in Relationships 中的示例:
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")
Dataclasses 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 版本中的新功能: 添加了对 “declared attr” 样式 mixin 属性的支持,即 relationship()
结构以及 Column
具有外键声明的对象,将在 “Dataclasses
with Declarative Table“ 样式映射。
使用 Imperative Mapping 映射预先存在的数据类¶
如前所述,使用
然后@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)
使用 Declarative With Imperative Table 映射预先存在的数据类中提到的相同警告
使用此映射样式时适用。
将 ORM 映射应用于现有的 attrs 类¶
attrs 库是一种流行的第三方库,它提供与数据类类似的功能,并提供了许多普通数据类中没有的附加功能。
使用 attrs 增强的类使用 @define
装饰器。此装饰器启动一个进程来扫描类以查找定义类行为的属性,然后使用这些属性生成方法、文档和注释。
SQLAlchemy ORM 支持使用 Declarative 和
Imperative Table 或 Imperative mapping。这两者的一般形式
styles 完全等同于
使用声明式字段映射预先存在的数据类,并且
使用声明性使用命令式表映射预先存在的数据类与数据类一起使用的表,其中数据类或 attrs 使用的内联属性指令保持不变,并且 SQLAlchemy 的面向表的插桩在运行时应用。
默认情况下,attrs 的 @define
装饰器将带注释的类替换为新的基于 __slots__ 的类,这是不支持的。当使用旧式 Comments @attr.s
或使用 define(slots=False)
时,该类不会被替换。此外,attrs 在装饰器运行后删除了自己的类绑定属性,因此 SQLAlchemy 的映射过程会毫无问题地接管这些属性。装饰器 @attr.s
和 @define (slots=False)
使用 SQLAlchemy。
将 attrs 与 Declarative “Imperative Table” 映射¶
在“Declarative with Imperative Table”样式中,一个 Table
object 与 Declarative 类内联声明。 这
首先将 @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__
映射类,不能与没有 完全
实施替代方案
属性插桩,因为映射类通常依赖于对 __dict__
的直接访问以进行状态存储。当存在此选项时,行为是未定义的。
使用 Imperative Mapping 映射 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)
上述形式等效于前面使用 Declarative 和 Imperative Table 的示例。