SqlAlchemy 2.0 中文文档(七)(4)

简介: SqlAlchemy 2.0 中文文档(七)

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 解释的指令(例如Columnrelationship())添加到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.idAddress.idAddress.user_id 属性被定义为 field(init=False)。这意味着这些参数不会被添加到 __init__() 方法中,但Session 仍然能够在从自增或其他默认值生成器刷新时获取它们的值并设置它们。为了允许在构造函数中明确指定它们,它们将被赋予 None 的默认值。

要单独声明一个 relationship(),需要将其直接指定在 Mapper.properties 字典中,该字典本身在 __mapper_args__ 字典中指定,以便将其传递给 Mapper 的构造函数。另一种方法在下一个示例中。

警告

声明一个 dataclass field() 设置一个 defaultinit=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()方法传递,以便我们可以将所有TableMapper配置命令式地传递给函数,而不是将它们定义为类本身的类变量:

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.idAddress.idAddress.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() 方法,这样我们就可以将所有 TableMapper 配置命令性地传递给函数,而不是将它们定义为类变量:

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

相关文章
|
4月前
|
SQL 前端开发 数据库
SqlAlchemy 2.0 中文文档(六)(1)
SqlAlchemy 2.0 中文文档(六)
45 0
|
4月前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(一)(1)
SqlAlchemy 2.0 中文文档(一)
156 1
SqlAlchemy 2.0 中文文档(一)(1)
|
4月前
|
SQL 测试技术 Python
SqlAlchemy 2.0 中文文档(四)(4)
SqlAlchemy 2.0 中文文档(四)
52 3
|
4月前
|
SQL 数据库 数据库管理
SqlAlchemy 2.0 中文文档(一)(2)
SqlAlchemy 2.0 中文文档(一)
124 1
|
4月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(一)(5)
SqlAlchemy 2.0 中文文档(一)
105 1
|
4月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(一)(4)
SqlAlchemy 2.0 中文文档(一)
66 1
|
4月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(十)(5)
SqlAlchemy 2.0 中文文档(十)
30 1
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(三)(2)
SqlAlchemy 2.0 中文文档(三)
32 1
|
4月前
|
SQL 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二)(3)
SqlAlchemy 2.0 中文文档(二)
42 2
|
4月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
70 2