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

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


原文:docs.sqlalchemy.org/en/20/contents.html

与 dataclasses 和 attrs 集成

原文:docs.sqlalchemy.org/en/20/orm/dataclasses.html

SQLAlchemy 从 2.0 版本开始具有“本地数据类”集成,在带注释的声明表映射中,可以通过向映射类添加单个 mixin 或装饰器将其转换为 Python dataclass

2.0 版本中的新功能:将数据类创建与 ORM 声明类集成

还有一些可用的模式,允许将现有的数据类映射,以及映射由第三方集成库attrs仪表化的类。

声明式数据类映射

SQLAlchemy 带注释的声明表映射可以通过附加的 mixin 类或装饰器指令进行扩展,在映射完成后将映射类原地转换为 Python dataclass,然后完成应用 ORM 特定的仪表化到类的映射过程。这提供的最突出的行为增加是生成具有对位置和关键字参数具有或不具有默认值的精细控制的__init__()方法,以及生成诸如__repr__()__eq__()等方法。

PEP 484的类型化角度来看,该类被认为具有特定于 Dataclass 的行为,最重要的是通过利用PEP 681“数据类转换”,使类型工具可以将该类视为明确使用了@dataclasses.dataclass装饰器装饰的类。

注意

2023 年 4 月 4 日之后,对于PEP 681在类型工具中的支持是有限的,目前已知由Pyright以及1.2 版Mypy支持。请注意,Mypy 1.1.1 引入了PEP 681支持,但未正确适配 Python 描述符,这将导致在使用 SQLAlchemy 的 ORM 映射方案时出现错误。

另请参阅

peps.python.org/pep-0681/#the-dataclass-transform-decorator - 有关像 SQLAlchemy 这样的库如何实现PEP 681支持的背景信息

数据类转换可以通过将MappedAsDataclass混入添加到DeclarativeBase类层次结构中的任何声明性类,或通过使用registry.mapped_as_dataclass()类装饰器进行装饰器映射来添加。

MappedAsDataclass混入可以应用于声明性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]

或者直接应用于从声明性基类扩展的类:

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]

类级特性配置

对于数据类功能的支持是部分的。目前支持的功能有initrepreqorderunsafe_hashmatch_argskw_only在 Python 3.10+上支持。目前不支持的功能有frozenslots

当使用混入类形式的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]

关于数据类类选项的背景,请参阅@dataclasses.dataclass中的dataclasses文档。

属性配置

SQLAlchemy 原生数据类与普通数据类的不同之处在于,要映射的属性在所有情况下都是使用Mapped泛型注解容器来描述的。映射遵循与带有 mapped_column()的声明性表中记录的相同形式,mapped_column()Mapped的所有功能都得到支持。

另外,ORM 属性配置结构,包括mapped_column()relationship()composite()支持每个属性字段选项,包括initdefaultdefault_factoryrepr。这些参数的名称是按照PEP 681中指定的固定的。功能与 dataclasses 相同:

  • init,如mapped_column.initrelationship.init,如果为 False,则表示该字段不应该是__init__()方法的一部分
  • default,比如mapped_column.defaultrelationship.default,表示字段的默认值,可以在__init__()方法中作为关键字参数给出。
  • default_factory,如mapped_column.default_factoryrelationship.default_factory,表示一个可调用的函数,如果未显式传递给__init__()方法,则将调用该函数生成参数的新默认值。
  • repr默认为 True,表示该字段应该是生成的__repr__()方法的一部分

与 dataclasses 的另一个关键区别是,属性的默认值必须使用 ORM 结构的default参数进行配置,例如mapped_column(default=None)。不支持类似于 dataclass 语法的语法,该语法接受简单的 Python 值作为默认值,而无需使用@dataclasses.field()

mapped_column()为例,下面的映射将产生一个__init__()方法,该方法仅接受namefullname字段,其中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 构造函数中现有的参数重叠,mapped_column() 构造函数通过添加一个新的参数 mapped_column.insert_default 来消除这两个名称的歧义,该参数将直接填充到 Column.default 参数中,独立于 mapped_column.default 的设置,后者始终用于数据类配置。例如,要配置一个日期时间列,其 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 593Annotated 对象将整个 mapped_column() 构造函数打包以供重用。此功能受数据类功能支持。但是,该功能的一个方面在使用类型工具时需要一个解决方法,即 PEP 681 特定参数 initdefaultreprdefault_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()

使用混合和抽象超类

MappedAsDataclass映射类中使用的任何混合类或基类,其中包括Mapped属性,必须本身是MappedAsDataclass层次结构的一部分,例如,在下面的示例中使用混合类:

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 类型检查器将不考虑来自非数据类混合类的属性作为数据类的一部分。

自 2.0.8 版已弃用:在MappedAsDataclassregistry.mapped_as_dataclass()层次结构中使用混合类和抽象基类,这些类本身不是数据类,因此这些字段不受PEP 681支持。对于此情况,将发出警告,以后将是一个错误。

另请参阅

将转换为数据类时,属性来自不是数据类的超类。 - 关于原因的背景

关系配置

当与relationship()结合使用时,Mapped注释的使用方式与基本关系模式中描述的方式相同。在指定基于集合的relationship()作为可选关键字参数时,必须传递relationship.default_factory参数,并且它必须指向要使用的集合类。如果默认值为None,则多对一和标量对象引用可以使用relationship.default

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_factoryrelationship.default 的存在决定了参数在转换为 __init__() 方法时是必需的还是可选的。

使用非映射数据类字段

当使用声明式数据类时,类上也可以使用非映射字段,这些字段将成为数据类构造过程的一部分,但不会被映射。任何不使用 Mapped 的字段都将被映射过程忽略。在下面的示例中,字段 ctrl_onectrl_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 类使用 idnamepassword_hash 作为映射特性,但使用只初始化的 passwordrepeat_password 字段来表示用户创建过程(注意:在运行此示例时,请将函数 your_crypt_function_here() 替换为第三方加密函数,例如 bcryptargon2-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)

上述对象使用了 passwordrepeat_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 注解的字段可能会被包括在内,这些字段将被视为结果数据类的一部分,但不会被映射,无需指定 __allow_unmapped__ 类属性也可以。以前的 2.0 beta 版本需要显式地包含此属性,即使此属性的目的仅是为了使传统的 ORM 类型映射继续正常工作。 ### 与 Pydantic 等备选数据类提供程序集成

警告

Pydantic 的数据类层与 SQLAlchemy 的类仪器不完全兼容,除非进行额外的内部更改,否则许多功能,如相关集合,可能无法正常工作。

为了与 Pydantic 兼容,请考虑使用基于 SQLAlchemy ORM 构建的SQLModel ORM,该 ORM 包含专门解决这些不兼容性的实现细节。

SQLAlchemy 的MappedAsDataclass类和registry.mapped_as_dataclass()方法直接调用 Python 标准库的dataclasses.dataclass类装饰器,此操作在将声明性映射过程应用于类之后进行。此函数调用可以通过MappedAsDataclass作为类关键字参数以及registry.mapped_as_dataclass()接受的dataclass_callable参数进行替换,以使用 Pydantic 等替代数据类提供程序:

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可调用。该过程既适用于映射类,也适用于从MappedAsDataclass扩展或直接应用registry.mapped_as_dataclass()的混入。

新功能版本 2.0.4:为MappedAsDataclassregistry.mapped_as_dataclass()添加了dataclass_callable类和方法参数,并调整了一些数据类内部,以适应更严格的数据类功能,例如 Pydantic 的功能。## 将 ORM 映射应用于现有数据类(旧版数据类用法)

遗留特性

此处描述的方法已被 2.0 系列 SQLAlchemy 中的声明性数据类映射功能取代。这一更新版本的功能是建立在首次添加到 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的构造函数。这种方法的另一种选择在下一个示例中。

警告

使用default设置一个defaultinit=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))})
使用声明性混合类型与预先存在的数据类

在使用混合类型构成映射层次结构部分中,引入了声明性混合类型类。声明性混合类型的一个要求是,某些不能轻易复制的构造必须作为可调用对象给出,使用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 表示 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对象,用于在“带有声明性表格的数据类”样式映射中使用。### 使用命令式映射映射预先存在的数据类

如前所述,使用 @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)

使用此映射样式时,请注意 Mapping pre-existing dataclasses using Declarative With Imperative Table 中提到的相同警告。## 对现有 attrs 类应用 ORM 映射

attrs 库是一个流行的第三方库,提供了类似 dataclasses 的功能,并提供了许多普通 dataclasses 中没有的附加功能。

使用 attrs 扩展的类使用 @define 装饰器。该装饰器启动一个过程来扫描类以定义类的行为的属性,然后用于生成方法、文档和注释。

SQLAlchemy ORM 支持使用 Declarative with Imperative TableImperative 映射来映射 attrs 类。这两种样式的一般形式与用于 dataclasses 的 Mapping pre-existing dataclasses using  Declarative-style fields 和 Mapping pre-existing dataclasses using  Declarative With Imperative Table 映射形式完全等效,其中 dataclasses 或 attrs  使用的内联属性指令保持不变,并且 SQLAlchemy 的面向表的仪器化在运行时应用。

attrs@define 装饰器默认用基于新 slots 的类替换带注释的类,这是不支持的。当使用旧样式注释 @attr.s 或使用 define(slots=False) 时,类不会被替换。此外,attrs 在装饰器运行后移除其自己的类绑定属性,以便 SQLAlchemy 的映射过程接管这些属性而不会出现任何问题。@attr.s@define(slots=False) 这两个装饰器都与 SQLAlchemy 兼容。


SqlAlchemy 2.0 中文文档(七)(2)https://developer.aliyun.com/article/1560922

相关文章
|
2天前
|
SQL 前端开发 数据库
SqlAlchemy 2.0 中文文档(六)(1)
SqlAlchemy 2.0 中文文档(六)
13 0
|
3天前
|
SQL 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二)(3)
SqlAlchemy 2.0 中文文档(二)
10 2
|
3天前
|
SQL 测试技术 Python
SqlAlchemy 2.0 中文文档(二)(1)
SqlAlchemy 2.0 中文文档(二)
14 2
|
3天前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(4)
SqlAlchemy 2.0 中文文档(二)
11 2
|
2天前
|
Python
SqlAlchemy 2.0 中文文档(七)(4)
SqlAlchemy 2.0 中文文档(七)
8 0
|
2天前
|
存储 SQL 数据库
SqlAlchemy 2.0 中文文档(七)(2)
SqlAlchemy 2.0 中文文档(七)
11 0
|
2天前
|
测试技术 API Python
SqlAlchemy 2.0 中文文档(六)(3)
SqlAlchemy 2.0 中文文档(六)
9 0
|
2天前
|
SQL 数据库 Python
SqlAlchemy 2.0 中文文档(六)(2)
SqlAlchemy 2.0 中文文档(六)
6 0
|
3天前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(5)
SqlAlchemy 2.0 中文文档(二)
8 0
|
2天前
|
SQL 测试技术 索引
SqlAlchemy 2.0 中文文档(六)(5)
SqlAlchemy 2.0 中文文档(六)
7 0