使用声明式的 Mapper 配置


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


使用声明式定义映射属性


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


虽然命令式映射利用 properties 字典来建立 所有映射的类属性,在声明性 mapping 中,这些属性都是在类定义中内联指定的, 在声明式表映射的情况下,它与 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 Deferral 限制哪些列加载


也可以使用 “hybrid table” 样式使用上述声明性映射来指定属性;直接属于表的 Column 对象将移动到 Table 定义中 但其他所有内容(包括组合的 SQL 表达式)仍将是 inline 的 INLINE 类定义。 需要引用 会直接引用它 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")


上面需要注意的事项:


  • address Table 包含一个名为 address_statistics 的列,但是我们将此列重新映射到相同的属性名称下,以便在 deferred() 结构的控制下。


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


  • 当我们定义 relationship() 结构时,由于这些结构在两个映射类之间创建了链接,其中一个必须在另一个之前定义,因此我们可以使用其字符串名称来引用远程类。此功能还扩展到 relationship() 上指定的其他参数区域,例如 “primary join” 和 “order by” 参数。有关详细信息,请参阅 Late-Evaluation of Relationship Arguments 部分。


具有声明式的 Mapper 配置选项


对于所有映射形式,类的映射都是通过成为 Mapper 对象一部分的参数来配置的。 最终接收这些参数的函数是 Mapper 函数,并从注册表上定义的一个正面映射函数传递给它 对象。


对于映射的声明形式,使用 __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 参数


__mapper_args__ 字典可以从类绑定 descriptor 方法,而不是从固定字典中通过使用 declared_attr() 构造。这对于为映射器创建参数非常有用,这些参数以编程方式从 table 配置或 mapped 类的其他方面派生。动态__mapper_args__ 属性通常在使用声明式 Mixin 或 abstract 基类。


例如,要从映射中省略任何具有特殊 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 对象 'exclude':True 传递给 Column.info 参数,然后将其字符串 “key” name 添加到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__() 类似,但在 mapper 配置开始时通过事件 MapperEvents.before_configured() 调用:

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


元数据


MetaData 集合通常用于为新的 Table 是与正在使用的注册表对象关联的 registry.metadata 属性。当使用 声明性基类,例如 DeclarativeBase 超类以及遗留函数(如 declarative_base()registry.generate_base() 中,此 MetaData 通常也以名为 .metadata 直接位于基类上,因此也通过继承位于映射类上。Declarative 使用此属性(如果存在)来确定目标 MetaData 集合,如果不存在,则使用直接与 注册表


此属性也可以分配给 for 以影响 MetaData 集合,用于单个基础和/或注册表的每个映射层次结构。无论使用声明式基类还是直接使用 registry.mapped() 装饰器,这都会生效,从而允许下一节 __abstract__ 中的 metadata-per-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__会导致 Declarative 完全跳过该类的 table 或 mapper 的生成。可以像 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__的一种可能用途是使用 不同基础的元数据

class Base(DeclarativeBase):
    pass


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


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


在上面,从 DefaultBase 继承的类将使用一个 MetaData 作为表的注册表,以及那些继承自 OtherBase 将使用不同的 OtherBase 。然后,可以在不同的数据库中创建 table 本身:

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 的情况,这会导致该类被视为单表继承而不是其子类。这在某些自定义方案中可能很有用,以确定应该根据 table 本身的参数进行单表继承,例如,如果没有主键,则定义为 single-inheritance:

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 表的成员。