使用声明式配置映射器

部分 映射类基本组件 讨论了 Mapper 结构的通用配置元素,该结构定义了如何将特定用户定义的类映射到数据库表或其他 SQL 结构。以下部分介绍了声明式系统如何构建 Mapper 的具体细节。

使用声明式定义映射属性

使用声明式配置表 中给出的示例说明了针对表绑定列的映射,使用了 mapped_column() 结构。除了表绑定列之外,还可以配置 ORM 映射结构的几种其他类型,最常见的是 relationship() 结构。其他类型的属性包括使用 column_property() 结构定义的 SQL 表达式,以及使用 composite() 结构进行的多列映射。

虽然 命令式映射 使用 属性 字典来建立所有映射的类属性,但在声明式映射中,这些属性都在类定义中内联定义,在声明式表映射的情况下,这些属性都在 Column 对象内联定义,这些对象将用于生成 Table 对象。

使用 UserAddress 的示例映射,我们可以说明一个声明式表映射,它不仅包含 mapped_column() 对象,还包含关系和 SQL 表达式

from typing import List
from typing import Optional

from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    firstname: Mapped[str] = mapped_column(String(50))
    lastname: Mapped[str] = mapped_column(String(50))
    fullname: Mapped[str] = column_property(firstname + " " + lastname)

    addresses: Mapped[List["Address"]] = relationship(back_populates="user")


class Address(Base):
    __tablename__ = "address"

    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    address_statistics: Mapped[Optional[str]] = mapped_column(Text, deferred=True)

    user: Mapped["User"] = relationship(back_populates="addresses")

上述声明式表映射具有两个表,每个表都使用 relationship() 引用另一个表,以及由 column_property() 映射的简单 SQL 表达式,以及一个额外的 mapped_column(),它指示加载应以 mapped_column.deferred 关键字定义的“延迟”方式进行。有关这些特定概念的更多文档,请参阅 基本关系模式使用 column_property使用列延迟限制加载的列

属性可以使用上述声明式映射通过“混合表”样式进行指定;直接属于表的 Column 对象移入 Table 定义,但所有其他内容,包括组合的 SQL 表达式,都将仍然在类定义中内联定义。需要直接引用 Column 的构造将根据 Table 对象进行引用。要说明上述映射使用混合表样式

# mapping attributes using declarative with imperative table
# i.e. __table__

from sqlalchemy import Column, ForeignKey, Integer, String, Table, Text
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import deferred
from sqlalchemy.orm import relationship


class Base(DeclarativeBase):
    pass


class User(Base):
    __table__ = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("firstname", String(50)),
        Column("lastname", String(50)),
    )

    fullname = column_property(__table__.c.firstname + " " + __table__.c.lastname)

    addresses = relationship("Address", back_populates="user")


class Address(Base):
    __table__ = Table(
        "address",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("user_id", ForeignKey("user.id")),
        Column("email_address", String),
        Column("address_statistics", Text),
    )

    address_statistics = deferred(__table__.c.address_statistics)

    user = relationship("User", back_populates="addresses")

上面需要注意的事项

  • 地址 Table 包含一个名为 address_statistics 的列,但我们将此列重新映射到相同的属性名称下,使其处于 deferred() 结构的控制之下。

  • 在声明式表和混合表映射中,当我们定义 ForeignKey 结构时,我们始终使用**表名**而不是映射的类名来命名目标表。

  • 当我们定义 relationship() 结构时,因为这些结构在两个映射的类之间创建了连接,其中一个必须在另一个之前定义,我们可以使用字符串名称引用远程类。此功能还扩展到 relationship() 上指定的其他参数,例如“主连接”和“排序”参数。有关此方面的详细信息,请参阅部分 关系参数的延迟评估

使用声明式配置映射器选项

对于所有映射形式,类的映射都是通过参数配置的,这些参数成为 Mapper 对象的一部分。最终接收这些参数的函数是 Mapper 函数,这些参数由 registry 对象上定义的前端映射函数传递给它。

对于声明式映射,映射器参数是使用 __mapper_args__ 声明式类变量指定的,它是一个字典,作为关键字参数传递给 Mapper 函数。以下是一些示例

映射特定主键列

以下示例说明了 Mapper.primary_key 参数的声明式级别设置,该参数将特定列建立为 ORM 应视为类的主键的一部分,与模式级主键约束无关

class GroupUsers(Base):
    __tablename__ = "group_users"

    user_id = mapped_column(String(40))
    group_id = mapped_column(String(40))

    __mapper_args__ = {"primary_key": [user_id, group_id]}

另请参阅

映射到显式主键列集 - 关于将显式列作为主键列进行 ORM 映射的更多背景信息

版本 ID 列

以下示例说明了 Mapper.version_id_colMapper.version_id_generator 参数的声明式级别设置,这些参数配置一个由 ORM 维护的版本计数器,该计数器在 工作单元 刷新过程中更新和检查

from datetime import datetime


class Widget(Base):
    __tablename__ = "widgets"

    id = mapped_column(Integer, primary_key=True)
    timestamp = mapped_column(DateTime, nullable=False)

    __mapper_args__ = {
        "version_id_col": timestamp,
        "version_id_generator": lambda v: datetime.now(),
    }

另请参阅

配置版本计数器 - 关于 ORM 版本计数器功能的背景信息

单表继承

以下示例说明了 Mapper.polymorphic_onMapper.polymorphic_identity 参数的声明式级别设置,这些参数用于配置单表继承映射

class Person(Base):
    __tablename__ = "person"

    person_id = mapped_column(Integer, primary_key=True)
    type = mapped_column(String, nullable=False)

    __mapper_args__ = dict(
        polymorphic_on=type,
        polymorphic_identity="person",
    )


class Employee(Person):
    __mapper_args__ = dict(
        polymorphic_identity="employee",
    )

另请参阅

单表继承 - 关于 ORM 单表继承映射功能的背景信息。

动态构建映射器参数

__mapper_args__ 字典可以通过使用 declared_attr() 结构从类绑定描述符方法生成,而不是从固定字典生成。这对于创建从表配置或映射类的其他方面以编程方式派生的映射器参数很有用。当使用声明式 Mixin 或抽象基类时,动态 __mapper_args__ 属性通常会有用。

例如,要从映射中省略具有特殊 Column.info 值的任何列,Mixin 可以使用一个 __mapper_args__ 方法,该方法从 cls.__table__ 属性中扫描这些列并将它们传递给 Mapper.exclude_properties 集合

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr


class ExcludeColsWFlag:
    @declared_attr
    def __mapper_args__(cls):
        return {
            "exclude_properties": [
                column.key
                for column in cls.__table__.c
                if column.info.get("exclude", False)
            ]
        }


class Base(DeclarativeBase):
    pass


class SomeClass(ExcludeColsWFlag, Base):
    __tablename__ = "some_table"

    id = mapped_column(Integer, primary_key=True)
    data = mapped_column(String)
    not_needed = mapped_column(String, info={"exclude": True})

上面,ExcludeColsWFlag Mixin 提供了一个每个类的 __mapper_args__ 钩子,它将扫描 Column 对象,这些对象包含传递给 Column.info 参数的键/值 'exclude': True,然后将其字符串“键”名称添加到 Mapper.exclude_properties 集合中,这将阻止生成的 Mapper 考虑这些列进行任何 SQL 操作。

其他声明式映射指令

__declare_last__()

__declare_last__() 钩子允许定义一个类级别函数,该函数由 MapperEvents.after_configured() 事件自动调用,该事件在假定映射完成并且“配置”步骤完成后发生

class MyClass(Base):
    @classmethod
    def __declare_last__(cls):
        """ """
        # do something with mappings

__declare_first__()

__declare_last__() 类似,但在映射器配置的开始通过 MapperEvents.before_configured() 事件调用

class MyClass(Base):
    @classmethod
    def __declare_first__(cls):
        """ """
        # do something before mappings are configured

metadata

通常用于分配新 TableMetaData 集合是与正在使用的 registry 对象关联的 registry.metadata 属性。当使用声明式基类(例如由 DeclarativeBase 超类生成的类)以及遗留函数(如 declarative_base()registry.generate_base())时,这个 MetaData 通常也作为名为 .metadata 的属性直接存在于基类上,因此也通过继承存在于映射类上。声明式使用这个属性(如果存在),来确定目标 MetaData 集合,或者如果不存在,则使用与 registry 直接关联的 MetaData

此属性也可以被分配以影响 MetaData 集合,以便在单个基类和/或 registry 的每个映射层次结构中使用。这将影响是否使用声明式基类或是否直接使用 registry.mapped() 装饰器,从而允许类似于下一节中的每个抽象基类的元数据示例的模式,__abstract__。可以使用 registry.mapped() 说明类似的模式,如下所示

reg = registry()


class BaseOne:
    metadata = MetaData()


class BaseTwo:
    metadata = MetaData()


@reg.mapped
class ClassOne:
    __tablename__ = "t1"  # will use reg.metadata

    id = mapped_column(Integer, primary_key=True)


@reg.mapped
class ClassTwo(BaseOne):
    __tablename__ = "t1"  # will use BaseOne.metadata

    id = mapped_column(Integer, primary_key=True)


@reg.mapped
class ClassThree(BaseTwo):
    __tablename__ = "t1"  # will use BaseTwo.metadata

    id = mapped_column(Integer, primary_key=True)

另请参阅

__abstract__

__abstract__

__abstract__ 使声明式完全跳过为类生成表或映射器。类可以像 Mixin 一样添加到层次结构中(参见 Mixin 和自定义基类),允许子类仅从特殊类继承

class SomeAbstractBase(Base):
    __abstract__ = True

    def some_helpful_method(self):
        """ """

    @declared_attr
    def __mapper_args__(cls):
        return {"helpful mapper arguments": True}


class MyMappedClass(SomeAbstractBase):
    pass

使用 __abstract__ 的一个可能用例是为不同的基类使用不同的 MetaData

class Base(DeclarativeBase):
    pass


class DefaultBase(Base):
    __abstract__ = True
    metadata = MetaData()


class OtherBase(Base):
    __abstract__ = True
    metadata = MetaData()

上面,从 DefaultBase 继承的类将使用一个 MetaData 作为表的注册表,而从 OtherBase 继承的类将使用另一个注册表。然后,这些表可以在不同的数据库中创建

DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)

另请参阅

使用 polymorphic_abstract 构建更深层的层次结构 - 适用于继承层次结构的“抽象”映射类的另一种形式。

__table_cls__

允许自定义用于生成 Table 的可调用对象/类。这是一个非常开放的钩子,它可以允许对在此生成的 Table 进行特殊自定义

class MyMixin:
    @classmethod
    def __table_cls__(cls, name, metadata_obj, *arg, **kw):
        return Table(f"my_{name}", metadata_obj, *arg, **kw)

上面的 Mixin 将导致所有生成的 Table 对象包含前缀 "my_",后面是使用 __tablename__ 属性通常指定的名称。

__table_cls__ 也支持返回 None 的情况,这会导致该类被视为单表继承,而不是其子类。这在某些自定义方案中可能很有用,以根据表格本身的参数来确定是否应该进行单表继承,例如,如果不存在主键,则将其定义为单继承。

class AutoTable:
    @declared_attr
    def __tablename__(cls):
        return cls.__name__

    @classmethod
    def __table_cls__(cls, *arg, **kw):
        for obj in arg[1:]:
            if (isinstance(obj, Column) and obj.primary_key) or isinstance(
                obj, PrimaryKeyConstraint
            ):
                return Table(*arg, **kw)

        return None


class Person(AutoTable, Base):
    id = mapped_column(Integer, primary_key=True)


class Employee(Person):
    employee_name = mapped_column(String)

上面的 Employee 类将被映射为针对 Person 的单表继承;employee_name 列将被添加为 Person 表的成员。