SqlAlchemy 2.0 中文文档(七)(4)https://developer.aliyun.com/article/1560926
将 ORM 映射应用到现有 attrs 类
attrs 库是一个流行的第三方库,提供了类似于 dataclasses 的功能,同时提供了许多在普通 dataclasses 中找不到的附加功能。
使用 attrs 增强的类使用 @define
装饰器。此装饰器启动一个过程,用于扫描类以查找定义类行为的属性,然后使用这些属性生成方法、文档和注释。
SQLAlchemy ORM 支持使用声明式与命令式表或命令式映射来映射 attrs 类。这两种风格的一般形式与数据类一起使用的使用声明式字段映射预先存在的数据类和使用声明式与命令式表映射预先存在的数据类的映射形式完全相同,其中数据类或 attrs 使用的内联属性指令保持不变,并且 SQLAlchemy 的面向表的仪器化在运行时应用。
attrs 的 @define
装饰器默认用新的基于 slots 的类替换注释类,这是不受支持的。当使用旧式注释 @attr.s
或使用 define(slots=False)
时,类不会被替换。此外,attrs 在装饰器运行后移除了自己的类绑定属性,以便 SQLAlchemy 的映射过程接管这些属性而不会出现任何问题。@attr.s
和 @define(slots=False)
两个装饰器都适用于 SQLAlchemy。
使用声明式“命令式表”映射属性
在“声明式与命令式表”风格中,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]] • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33 • 34 • 35 • 36 • 37 • 38 • 39 • 40 • 41 • 42 • 43 • 44 • 45 • 46 • 47 • 48 • 49 • 50 • 51 • 52 • 53 • 54
注意
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) • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33 • 34 • 35 • 36 • 37 • 38 • 39 • 40 • 41 • 42 • 43 • 44 • 45 • 46 • 47 • 48 • 49 • 50 • 51 • 52 • 53 • 54 • 55 • 56 • 57 • 58
上述形式等同于先前使用声明式与命令式表的示例。
使用声明式“命令式表”映射属性
在“声明式与命令式表”风格中,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]] • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33 • 34 • 35 • 36 • 37 • 38 • 39 • 40 • 41 • 42 • 43 • 44 • 45 • 46 • 47 • 48 • 49 • 50 • 51 • 52 • 53 • 54
注意
attrs
的 slots=True
选项可以在映射类上启用 __slots__
,但在不完全实现替代的属性调试时,无法与 SQLAlchemy 映射一起使用,因为映射类通常依赖于直接访问 __dict__
来存储状态。当存在此选项时,行为是未定义的。
使用命令式映射(Imperative Mapping)进行属性映射
就像在数据类中一样,我们可以利用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) • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33 • 34 • 35 • 36 • 37 • 38 • 39 • 40 • 41 • 42 • 43 • 44 • 45 • 46 • 47 • 48 • 49 • 50 • 51 • 52 • 53 • 54 • 55 • 56 • 57 • 58
上述形式等同于使用命令式表格进行声明的上一个示例。