SqlAlchemy 2.0 中文文档(七)(3)https://developer.aliyun.com/article/1560924
将 ORM 映射应用于现有数据类(旧数据类使用)
遗留特性
这里描述的方法已被 SQLAlchemy 2.0 系列中的声明性数据类映射特性取代。该特性的新版本建立在首次在 1.4 版本中添加的数据类支持之上,该支持在本节中描述。
要映射现有的数据类,不能直接使用 SQLAlchemy 的“内联”声明性指令;ORM 指令是使用以下三种技术之一分配的:
- 使用“带命令式表”的方法,要映射的表/列是使用分配给类的
__table__
属性的Table
对象定义的;关系在__mapper_args__
字典中定义。使用registry.mapped()
装饰器映射类。下面是一个示例,在使用带命令式表的方式映射预先存在的数据类中。 - 使用完整的“声明式”,将 Declarative 解释的指令(例如
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
的构造函数。另一种方法在下一个示例中。
警告
声明一个 dataclass field()
设置一个 default
与 init=False
一起使用不会像完全普通的 dataclass 那样起作用,因为 SQLAlchemy 类的内部机制会用数据类创建过程中设置的默认值替换类上的默认值。使用 default_factory
代替。当使用 声明式 Dataclass 映射 时,此适应将自动完成。### 使用声明式字段映射现有数据类
旧版特性
应将此数据类与声明式映射一起使用的方法视为旧版。它将继续受到支持,但是不太可能提供与 声明式 Dataclass 映射 中详细说明的新方法相比的任何优势。
注意**mapped_column()
在这种用法下不受支持**;应继续使用Column
构造函数来声明 metadata
字段中的表元数据。
完全声明式方法要求将 Column
对象声明为类属性,在使用 dataclasses 时会与 dataclass 级别的属性冲突。结合这些的一种方法是利用 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 与现有数据类
在 使用 Mixins 构建映射层次结构 部分介绍了声明式 Mixin 类。声明式 Mixin 的一个要求是,某些无法轻松复制的构造必须作为可调用对象给出,使用 declared_attr
装饰器,例如在 混合关系 中的示例:
class RefTargetMixin: @declared_attr def target_id(cls): return Column("target_id", ForeignKey("target.id")) @declared_attr def target(cls): return relationship("Target")
通过在数据类field()
对象中使用 lambda 来表示field()
内的 SQLAlchemy 构造支持此形式。使用declared_attr()
将 lambda 包围起来是可选的。如果我们想要生成上述的User
类,其中 ORM 字段来自于一个自身是数据类的混合类,形式将是:
@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 中新增:对“已声明属性”样式的混合属性提供支持,即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)
使用此映射样式时,请注意使用声明式带命令式表映射预先存在的数据类中提到的相同警告。### 使用声明式带命令式表映射预先存在的数据类
下面是使用 声明式和命令式表格(也称为混合声明式) 的 @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
仍然可以在 flush 期间从自增或其他默认值生成器获取它们的值后设置它们。为了允许在构造函数中显式指定它们,它们将被给定一个默认值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 类。声明式 mixins 的一个要求是,某些无法轻松复制的构造必须以可调用的形式给出,使用 declared_attr
装饰器,例如在 混合关系 中的示例:
class RefTargetMixin: @declared_attr def target_id(cls): return Column("target_id", ForeignKey("target.id")) @declared_attr def target(cls): return relationship("Target")
此形式在 Dataclasses 的 field()
对象中得到支持,通过使用 lambda 来指示 field()
内部的 SQLAlchemy 构造。在 lambda 周围使用 declared_attr()
是可选的。如果我们想要生成我们上面的 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
对象。 #### 使用具有预先存在的数据类的声明式混合
在 使用混合组合映射层次结构 部分介绍了声明式 Mixin 类。声明式 mixins 的一个要求是,某些无法轻松复制的构造必须以可调用的形式给出,使用 declared_attr
装饰器,例如在 混合关系 中的示例:
class RefTargetMixin: @declared_attr def target_id(cls): return Column("target_id", ForeignKey("target.id")) @declared_attr def target(cls): return relationship("Target")
在 Dataclasses field()
对象中支持此形式,通过使用 lambda 表示 SQLAlchemy 构造在 field()
内部。使用 declared_attr()
将 lambda 包围起来是可选的。如果我们想要生成上述的 User
类,其中 ORM 字段来自于一个自身是 dataclass 的 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
如前所述,通过使用 @dataclass
装饰器设置为 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)
在使用此映射样式时,与使用声明性与命令性表映射现有数据类中提到的相同警告适用。
SqlAlchemy 2.0 中文文档(七)(5)https://developer.aliyun.com/article/1560928