SqlAlchemy 2.0 中文文档(六)

2024-06-26 14:28:01 浏览数 (2)

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

声明式的 Mapper 配置

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

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

使用声明式定义映射属性

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

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

在示例映射的 UserAddress 上工作时,我们可以演示一个声明性表映射,其中不仅包括 mapped_column() 对象,还包括关系和 SQL 表达式:

代码语言:javascript复制
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(),以及一个简单的 SQL 表达式由column_property()映射,还有一个额外的mapped_column(),它指示加载应根据mapped_column.deferred 关键字的定义进行“延迟”。有关这些特定概念的更多文档可以在基本关系模式、使用 column_property 和限制哪些列使用列延迟加载中找到。

属性可以使用上述的声明式映射以“混合表”风格指定;直接属于表的Column 对象移到Table 定义中,但包括组成的 SQL 表达式在内的其他所有内容仍将与类定义内联。需要直接引用Column 的构造将使用Table 对象来引用它。使用混合表风格进行上述映射的示例如下:

代码语言:javascript复制
# 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()上指定的其他参数,如“primary join”和“order by”参数。有关详细信息,请参阅延迟评估关系参数章节。## 使用声明配置选项的映射器

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

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

映射特定的主键列

下面的示例说明了Mapper.primary_key参数的声明级设置,该参数将特定列作为 ORM 应考虑为类的主键的一部分,而不受架构级主键约束的影响:

代码语言:javascript复制
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 维护的版本计数器,在工作单元刷新过程中更新和检查:

代码语言:javascript复制
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 参数的声明级别设置:

代码语言:javascript复制
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() 构造而不是固定字典而生成。通过此方式生成 __mapper_args__ 对于从表配置或映射类的其他方面程序化派生映射器参数非常有用。动态 __mapper_args__ 属性通常在使用声明性混合或抽象基类时非常有用。

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

代码语言:javascript复制
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 混合类提供了一个每个类的 __mapper_args__ 钩子,该钩子将扫描包含传递给 Column.info 参数的键/值 'exclude': TrueColumn 对象,然后将其字符串“键”名称添加到 Mapper.exclude_properties 集合中,这将防止生成的 Mapper 考虑这些列进行任何 SQL 操作。

另请参阅

使用混合组合映射层次结构

其他声明性映射指令

__declare_last__()

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

代码语言:javascript复制
class MyClass(Base):
    @classmethod
    def __declare_last__(cls):
  """ """
        # do something with mappings
__declare_first__()

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

代码语言:javascript复制
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

这个属性还可以分配给单个基类和/或registry以影响每个映射层次结构的MetaData集合的使用。这对于使用声明基类或直接使用registry.mapped()装饰器的情况都会生效,从而允许如下所示的每个抽象基类的元数据模式示例,在下一节 abstract。可以使用registry.mapped()来说明类似的模式:

代码语言:javascript复制
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 and Custom Base Classes),允许子类仅从特殊类扩展:

代码语言:javascript复制
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

代码语言:javascript复制
class Base(DeclarativeBase):
    pass

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

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

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

代码语言:javascript复制
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)

另请参阅

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

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

代码语言:javascript复制
class MyMixin:
    @classmethod
    def __table_cls__(cls, name, metadata_obj, *arg, **kw):
        return Table(f"my_{name}", metadata_obj, *arg, **kw)

上述的混合类将导致所有生成的 Table 对象都包含前缀 "my_",后跟通常使用 __tablename__ 属性指定的名称。

__table_cls__ 还支持返回 None 的情况,这会导致将该类视为单表继承与其子类。这在某些定制方案中可能很有用,以确定是否应该基于表本身的参数来执行单表继承,例如,如果没有主键存在,则定义为单继承:

代码语言:javascript复制
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 类将被映射为单表继承,对应于 Personemployee_name 列将被添加为 Person 表的成员。## 使用声明式定义映射属性

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

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

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

代码语言:javascript复制
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 关键字定义为“deferred”。有关这些特定概念的更多文档可在基本关系模式、使用 column_property 和使用列推迟限制加载的列中找到。

使用声明式映射,可以像上面那样使用“混合表”样式来指定属性;直接属于表的 Column 对象移入 Table 定义,但包括组合 SQL 表达式在内的其他所有内容仍将内联到类定义中。需要引用 Column 的构造将以 Table 对象的术语引用它。为了使用混合表样式说明上述映射:

代码语言:javascript复制
# 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() 上指定的其他参数,如“主连接”和“排序”参数。有关详细信息,请参阅 Relationship 参数的延迟评估 部分。

带有声明性的 Mapper 配置选项

对于所有映射形式,类的映射通过成为 Mapper 对象的一部分的参数进行配置。最终接收这些参数的函数是 Mapper 函数,并且它们从定义在 registry 对象上的一个前置映射函数中传递给它。

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

映射特定的主键列

下面的示例说明了 Mapper.primary_key 参数的声明级别设置,它将特定列作为 ORM 应该考虑为类的主键的一部分,与架构级主键约束独立:

代码语言:javascript复制
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 维护的版本计数器,在 工作单元 刷新过程中进行更新和检查:

代码语言:javascript复制
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 参数的声明级别设置:

代码语言:javascript复制
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() 构造而不是固定字典从类绑定描述符方法生成。这对于从表配置或映射类的其他方面编程派生映射器参数非常有用。动态的 __mapper_args__ 属性通常在使用声明性 Mixin 或抽象基类时非常有用。

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

代码语言:javascript复制
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.info 参数的键/值 'exclude': TrueColumn 对象,然后将它们的字符串“键”名称添加到 Mapper.exclude_properties 集合中,这将阻止生成的 Mapper 对这些列进行任何 SQL 操作的考虑。

另见

使用 Mixin 组合映射层次结构

动态构建映射器参数

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

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

代码语言:javascript复制
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.info参数的键/值'exclude': TrueColumn对象,然后将其字符串“key”名称添加到Mapper.exclude_properties集合中,这将阻止生成的Mapper考虑这些列进行任何 SQL 操作。

另请参阅

使用 Mixins 构建映射的分层结构

其他声明性映射指令

__declare_last__()

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

代码语言:javascript复制
class MyClass(Base):
    @classmethod
    def __declare_last__(cls):
  """ """
        # do something with mappings
__declare_first__()

__declare_last__()一样,但是通过MapperEvents.before_configured()事件在映射器配置的开始时调用:

代码语言:javascript复制
class MyClass(Base):
    @classmethod
    def __declare_first__(cls):
  """ """
        # do something before mappings are configured
metadata

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

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

代码语言:javascript复制
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 and Custom Base Classes),允许子类仅从特殊类扩展:

代码语言:javascript复制
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

代码语言:javascript复制
class Base(DeclarativeBase):
    pass

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

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

在上述示例中,从 DefaultBase 继承的类将使用一个 MetaData 作为表的注册表,而从 OtherBase 继承的类将使用不同的注册表。然后,表本身可能会被创建在不同的数据库中:

代码语言:javascript复制
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)

另请参见

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

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

代码语言:javascript复制
class MyMixin:
    @classmethod
    def __table_cls__(cls, name, metadata_obj, *arg, **kw):
        return Table(f"my_{name}", metadata_obj, *arg, **kw)

上述的混合类将导致所有生成的 Table 对象都包含前缀 "my_",后跟通常使用 __tablename__ 属性指定的名称。

__table_cls__ 还支持返回 None 的情况,这会导致将类视为单表继承 vs. 其子类。在某些定制方案中,这可能是有用的,以确定应基于表本身的参数进行单表继承,例如,如果不存在主键,则定义为单继承:

代码语言:javascript复制
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 表的成员添加。

__declare_last__()

__declare_last__() 钩子允许定义一个类级别的函数,该函数会在 MapperEvents.after_configured() 事件之后自动调用,该事件发生在映射被认为已完成并且“配置”步骤已经结束之后:

代码语言:javascript复制
class MyClass(Base):
    @classmethod
    def __declare_last__(cls):
  """ """
        # do something with mappings
__declare_first__()

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

代码语言:javascript复制
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()来说明如下:

代码语言:javascript复制
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 and Custom Base Classes),从而允许子类仅从特殊类扩展:

代码语言:javascript复制
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

代码语言:javascript复制
class Base(DeclarativeBase):
    pass

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

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

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

代码语言:javascript复制
DefaultBase.metadata.create_all(some_engine)
OtherBase.metadata.create_all(some_other_engine)

另请参阅

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

__table_cls__

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

代码语言:javascript复制
class MyMixin:
    @classmethod
    def __table_cls__(cls, name, metadata_obj, *arg, **kw):
        return Table(f"my_{name}", metadata_obj, *arg, **kw)

上述混合类将导致所有生成的 Table 对象都包含前缀 "my_",后跟通常使用 __tablename__ 属性指定的名称。

__table_cls__ 也支持返回 None 的情况,这将导致将类视为单表继承与其子类。在一些定制方案中,这可能是有用的,以确定基于表本身的参数是否应该进行单表继承,例如,如果没有主键存在,则定义为单继承:

代码语言:javascript复制
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 表的成员添加。

使用混合类组合映射层次结构

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

在使用 Declarative 风格映射类时,常见的需求是共享常见功能,例如特定列、表或映射器选项、命名方案或其他映射属性,跨多个类。在使用声明性映射时,可以通过使用 mixin 类,以及通过扩展声明性基类本身来支持此习惯用法。

提示

除了 mixin 类之外,还可以使用PEP 593 Annotated 类型共享许多类的常见列选项;请参阅将多种类型配置映射到 Python 类型和将整个列声明映射到 Python 类型以获取有关这些 SQLAlchemy 2.0 功能的背景信息。

以下是一些常见的混合用法示例:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
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 CommonMixin:
  """define a series of common elements that may be applied to mapped
 classes using this class as a mixin class."""

    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

    __table_args__ = {"mysql_engine": "InnoDB"}
    __mapper_args__ = {"eager_defaults": True}

    id: Mapped[int] = mapped_column(primary_key=True)

class HasLogRecord:
  """mark classes that have a many-to-one relationship to the
 ``LogRecord`` class."""

    log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))

    @declared_attr
    def log_record(self) -> Mapped["LogRecord"]:
        return relationship("LogRecord")

class LogRecord(CommonMixin, Base):
    log_info: Mapped[str]

class MyModel(CommonMixin, HasLogRecord, Base):
    name: Mapped[str]

上述示例说明了一个类MyModel,它包括两个混合类CommonMixinHasLogRecord,以及一个补充类LogRecord,该类也包括CommonMixin,演示了在混合类和基类上支持的各种构造,包括:

  • 使用mapped_column()MappedColumn声明的列将从混合类或基类复制到要映射的目标类;上面通过列属性CommonMixin.idHasLogRecord.log_record_id说明了这一点。
  • 可以将声明性指令(如__table_args____mapper_args__)分配给混合类或基类,在继承混合类或基类的任何类中,这些指令将自动生效。上述示例使用__table_args____mapper_args__属性说明了这一点。
  • 所有的 Declarative 指令,包括 __tablename____table____table_args____mapper_args__,都可以使用用户定义的类方法来实现,这些方法使用了 declared_attr 装饰器进行修饰(具体地说是 declared_attr.directive 子成员,稍后会详细介绍)。上面的示例使用了一个 def __tablename__(cls) 类方法动态生成一个 Table 名称;当应用到 MyModel 类时,表名将生成为 "mymodel",而当应用到 LogRecord 类时,表名将生成为 "logrecord"
  • 其他 ORM 属性,如 relationship(),也可以通过在目标类上生成的用户定义的类方法来生成,并且这些类方法也使用了 declared_attr 装饰器进行修饰。上面的例子演示了通过生成一个多对一的 relationship() 到一个名为 LogRecord 的映射对象来实现此功能。

上述功能都可以使用 select() 示例进行演示:

代码语言:javascript复制
>>> from sqlalchemy import select
>>> print(select(MyModel).join(MyModel.log_record))
SELECT  mymodel.name,  mymodel.id,  mymodel.log_record_id
FROM  mymodel  JOIN  logrecord  ON  logrecord.id  =  mymodel.log_record_id 

提示

declared_attr 的示例将尝试说明每个方法示例的正确的 PEP 484 注解。使用 declared_attr 函数的注解是完全可选的,并且不会被 Declarative 消耗;然而,为了通过 Mypy 的 --strict 类型检查,这些注解是必需的。

另外,上面所示的 declared_attr.directive 子成员也是可选的,它只对 PEP 484 类型工具有意义,因为它调整了创建用于重写 Declarative 指令的方法时的期望返回类型,例如 __tablename____mapper_args____table_args__

新版本 2.0 中新增:作为 SQLAlchemy ORM 的 PEP 484 类型支持的一部分,添加了 declared_attr.directive 来将 declared_attr 区分为 Mapped 属性和声明性配置属性之间的区别。

混合类和基类的顺序没有固定的约定。普通的 Python 方法解析规则适用,上述示例也同样适用:

代码语言:javascript复制
class MyModel(Base, HasLogRecord, CommonMixin):
    name: Mapped[str] = mapped_column()

这是因为这里的 Base 没有定义 CommonMixinHasLogRecord 定义的任何变量,即 __tablename____table_args__id 等。如果 Base 定义了同名属性,则位于继承列表中的第一个类将决定在新定义的类上使用哪个属性。

提示

虽然上述示例使用了基于 Mapped 注解类的注释声明表形式,但混合类与非注释和遗留声明形式也完全兼容,比如直接使用 Column 而不是 mapped_column() 时。

从版本 2.0 开始更改:对于从 SQLAlchemy 1.4 系列迁移到的用户可能一直在使用 mypy 插件,不再需要使用 declarative_mixin() 类装饰器来标记声明性混合类,假设不再使用 mypy 插件。

扩充基类

除了使用纯混合类之外,本节中的大多数技术也可以直接应用于基类,用于适用于从特定基类派生的所有类的模式。下面的示例演示了上一节中的一些示例在 Base 类方面的情况:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
  """define a series of common elements that may be applied to mapped
 classes using this class as a base class."""

    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

    __table_args__ = {"mysql_engine": "InnoDB"}
    __mapper_args__ = {"eager_defaults": True}

    id: Mapped[int] = mapped_column(primary_key=True)

class HasLogRecord:
  """mark classes that have a many-to-one relationship to the
 ``LogRecord`` class."""

    log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))

    @declared_attr
    def log_record(self) -> Mapped["LogRecord"]:
        return relationship("LogRecord")

class LogRecord(Base):
    log_info: Mapped[str]

class MyModel(HasLogRecord, Base):
    name: Mapped[str]

上述,MyModel 以及 LogRecord,在从 Base 派生时,它们的表名都将根据其类名派生,一个名为 id 的主键列,以及由 Base.__table_args__Base.__mapper_args__ 定义的上述表和映射器参数。

当使用遗留 declarative_base()registry.generate_base() 时,可以像下面的非注释示例中所示使用 declarative_base.cls 参数来生成等效效果:

代码语言:javascript复制
# legacy declarative_base() use

from sqlalchemy import Integer, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base:
  """define a series of common elements that may be applied to mapped
 classes using this class as a base class."""

    @declared_attr.directive
    def __tablename__(cls):
        return cls.__name__.lower()

    __table_args__ = {"mysql_engine": "InnoDB"}
    __mapper_args__ = {"eager_defaults": True}

    id = mapped_column(Integer, primary_key=True)

Base = declarative_base(cls=Base)

class HasLogRecord:
  """mark classes that have a many-to-one relationship to the
 ``LogRecord`` class."""

    log_record_id = mapped_column(ForeignKey("logrecord.id"))

    @declared_attr
    def log_record(self):
        return relationship("LogRecord")

class LogRecord(Base):
    log_info = mapped_column(String)

class MyModel(HasLogRecord, Base):
    name = mapped_column(String)

混合列

如果使用声明式表 风格的配置(而不是命令式表 配置),则可以在混合中指定列,以便混合中声明的列随后将被复制为声明式进程生成的Table 的一部分。在声明式混合中可以内联声明三种构造:mapped_column()MappedColumn

代码语言:javascript复制
class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(default=func.now())
    updated_at: Mapped[datetime]

class MyModel(TimestampMixin, Base):
    __tablename__ = "test"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

在上述位置,所有包含TimestampMixin在其类基础中的声明性类将自动包含一个应用于所有行插入的时间戳的列created_at,以及一个updated_at列,该列不包含示例目的的默认值(如果有的话,我们将使用Column.onupdate 参数,该参数由mapped_column() 接受)。这些列结构始终是从原始混合或基类复制的,因此相同的混合/基类可以应用于任意数量的目标类,每个目标类都将具有自己的列结构。

所有声明式列形式都受混合支持,包括:

带有注解的属性 - 无论是否存在mapped_column()

代码语言:javascript复制
class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(default=func.now())
    updated_at: Mapped[datetime]

mapped_column - 无论是否存在 Mapped

代码语言:javascript复制
class TimestampMixin:
    created_at = mapped_column(default=func.now())
    updated_at: Mapped[datetime] = mapped_column()

- 传统的声明式形式:

代码语言:javascript复制
class TimestampMixin:
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime)

在上述每种形式中,声明式通过创建构造的副本来处理混合类上的基于列的属性,然后将其应用于目标类。

自版本 2.0 起更改:声明式 API 现在可以容纳Column 对象以及使用混合时的任何形式的mapped_column() 构造,而无需使用declared_attr()。已经删除了以前的限制,该限制阻止具有ForeignKey 元素的列直接在混合中使用。

混入关系

通过relationship()创建的关系,仅使用declared_attr方法提供声明性混合类,从而消除了复制关系及其可能绑定到列的内容时可能出现的任何歧义。下面是一个示例,其中结合了外键列和关系,以便两个类FooBar都可以通过多对一引用到一个公共目标类:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class RefTargetMixin:
    target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))

    @declared_attr
    def target(cls) -> Mapped["Target"]:
        return relationship("Target")

class Foo(RefTargetMixin, Base):
    __tablename__ = "foo"
    id: Mapped[int] = mapped_column(primary_key=True)

class Bar(RefTargetMixin, Base):
    __tablename__ = "bar"
    id: Mapped[int] = mapped_column(primary_key=True)

class Target(Base):
    __tablename__ = "target"
    id: Mapped[int] = mapped_column(primary_key=True)

使用上面的映射,FooBar中的每一个都包含一个访问.target属性的到Target的关系:

代码语言:javascript复制
>>> from sqlalchemy import select
>>> print(select(Foo).join(Foo.target))
SELECT  foo.id,  foo.target_id
FROM  foo  JOIN  target  ON  target.id  =  foo.target_id
>>> print(select(Bar).join(Bar.target))
SELECT  bar.id,  bar.target_id
FROM  bar  JOIN  target  ON  target.id  =  bar.target_id 

特殊参数,比如relationship.primaryjoin,也可以在混合类方法中使用,这些方法通常需要引用正在映射的类。对于需要引用本地映射列的方案,在普通情况下,这些列将作为 Declarative 的属性在映射类上提供,并作为传递给装饰类方法的cls参数。利用这个特性,我们可以例如重新编写RefTargetMixin.target方法,使用明确的 primaryjoin,它引用了Targetcls上的待定映射列:

代码语言:javascript复制
class Target(Base):
    __tablename__ = "target"
    id: Mapped[int] = mapped_column(primary_key=True)

class RefTargetMixin:
    target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))

    @declared_attr
    def target(cls) -> Mapped["Target"]:
        # illustrates explicit 'primaryjoin' argument
        return relationship("Target", primaryjoin=Target.id == cls.target_id)
```## 混合使用`column_property()` 和其他 `MapperProperty` 类

像`relationship()`一样,其他的`MapperProperty`子类,比如`column_property()`,在混合使用时也需要生成类本地副本,因此也在被`declared_attr`装饰的函数中声明。在函数内部,使用`mapped_column()`、`Mapped`或`Column`声明的其他普通映射列将从`cls`参数中提供,以便可以用于组合新的属性,如下面的示例,将两列相加:

```py
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class SomethingMixin:
    x: Mapped[int]
    y: Mapped[int]

    @declared_attr
    def x_plus_y(cls) -> Mapped[int]:
        return column_property(cls.x   cls.y)

class Something(SomethingMixin, Base):
    __tablename__ = "something"

    id: Mapped[int] = mapped_column(primary_key=True)

在上面的示例中,我们可以在语句中使用Something.x_plus_y,产生完整的表达式:

代码语言:javascript复制
>>> from sqlalchemy import select
>>> print(select(Something.x_plus_y))
SELECT  something.x     something.y  AS  anon_1
FROM  something 

提示

declared_attr 装饰器使装饰的可调用对象的行为与类方法完全相同。然而,像Pylance这样的类型工具可能无法识别这一点,有时会因为无法访问函数体内的 cls 变量而抱怨。要解决此问题,当发生时,可以直接将 @classmethod 装饰器与declared_attr 结合使用,如下所示:

代码语言:javascript复制
class SomethingMixin:
    x: Mapped[int]
    y: Mapped[int]

    @declared_attr
    @classmethod
    def x_plus_y(cls) -> Mapped[int]:
        return column_property(cls.x   cls.y)

新版本 2.0 中:- declared_attr 可以适应使用 @classmethod 装饰的函数来协助PEP 484集成的需要。## 使用混合和基类进行映射继承模式

在处理如映射类继承层次结构中记录的映射器继承模式时,当使用 declared_attr 时,可以使用一些附加功能,无论是与混合类一起使用,还是在类层次结构中增加映射和未映射的超类时。

当在映射继承层次结构中由子类解释的函数由declared_attr装饰在混合或基类上定义时,必须区分两种情况,即生成 Declarative 使用的特殊名称如 __tablename____mapper_args__ 与生成普通映射属性如mapped_column()relationship()。定义 Declarative 指令 的函数会 在层次结构中的每个子类中调用,而生成 映射属性 的函数只会 在层次结构中的第一个映射的超类中调用

此行为差异的基本原理是,映射属性已经可以被类继承,例如,超类映射表上的特定列不应该在子类中重复出现,而特定于特定类或其映射表的元素不可继承,例如,局部映射的表的名称。

这两种用例之间行为上的差异在以下两个部分中得到了展示。

使用declared_attr()结合继承的TableMapper参数

使用 mixin 的一个常见方法是创建一个 def __tablename__(cls) 函数,动态生成映射的 Table 名称。

这个方法可以用于生成继承映射层次结构中的表名称,就像下面的示例一样,该示例创建一个 mixin,根据类名给每个类生成一个简单的表名称。下面的示例说明了如何为 Person 映射类和 PersonEngineer 子类生成表名称,但不为 PersonManager 子类生成表名称:

代码语言:javascript复制
from typing import Optional

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
  """override __tablename__ so that Manager is single-inheritance to Person"""

        return None

    __mapper_args__ = {"polymorphic_identity": "manager"}

在上述示例中,Person 基类和 Engineer 类都是 Tablename mixin 类的子类,该类生成新的表名称,因此它们都将具有生成的 __tablename__ 属性。对于 Declarative,这意味着每个类都应该有自己的 Table 生成,并将其映射到其中。对于 Engineer 子类,所应用的继承风格是联合表继承,因为它将映射到一个与基本 person 表连接的 engineer 表。从 Person 继承的任何其他子类也将默认应用此继承风格(在此特定示例中,每个子类都需要指定一个主键列;关于这一点,后面会详细介绍)。

相比之下,PersonManager 子类覆盖__tablename__ 类方法,将其返回值设为 None。这表明对于 Declarative 来说,该类不应生成一个 Table,而应仅使用 Person 映射到的基本 Table。对于 Manager 子类,所应用的继承风格是单表继承。

上面的示例说明了 Declarative 指令(如 __tablename__)必须分别应用于每个子类,因为每个映射类都需要说明将映射到哪个 Table,或者是否将自身映射到继承的超类的 Table

如果我们希望反转上面说明的默认表方案,使得单表继承成为默认,只有在提供了 __tablename__ 指令以覆盖它时才能定义连接表继承,我们可以在顶级 __tablename__() 方法中使用 Declarative 助手,本例中称为 has_inherited_table()。此函数将返回 True 如果超类已经映射到一个 Table。我们可以在基类中的最低级 __tablename__() 类方法中使用此辅助函数,以便我们有条件地如果表已经存在,则返回 None 作为表名,从而默认为继承子类的单表继承:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls):
        if has_inherited_table(cls):
            return None
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    @declared_attr.directive
    def __tablename__(cls):
  """override __tablename__ so that Engineer is joined-inheritance to Person"""

        return cls.__name__.lower()

    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    __mapper_args__ = {"polymorphic_identity": "manager"}
使用 declared_attr() 生成特定于表的继承列

与在与 declared_attr 一起使用时如何处理 __tablename__ 和其他特殊名称不同,当我们混入列和属性(例如关系、列属性等)时,该函数仅在层次结构中的基类调用,除非结合使用 declared_attr 指令和 declared_attr.cascading 子指令。在下面的示例中,只有 Person 类将收到名为 id 的列;对于未给出主键的 Engineer,映射将失败:

代码语言:javascript复制
class HasId:
    id: Mapped[int] = mapped_column(primary_key=True)

class Person(HasId, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

# this mapping will fail, as there's no primary key
class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

在连接表继承中,通常情况下我们希望每个子类具有不同命名的列。然而在这种情况下,我们可能希望每个表上都有一个 id 列,并且通过外键相互引用。我们可以通过使用 declared_attr.cascading 修饰符作为 mixin 来实现这一点,该修饰符表示应该对层次结构中的每个类调用该函数,几乎(见下面的警告)与对 __tablename__ 调用方式相同:

代码语言:javascript复制
class HasIdMixin:
    @declared_attr.cascading
    def id(cls) -> Mapped[int]:
        if has_inherited_table(cls):
            return mapped_column(ForeignKey("person.id"), primary_key=True)
        else:
            return mapped_column(Integer, primary_key=True)

class Person(HasIdMixin, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

警告

目前,declared_attr.cascading 功能不允许子类使用不同的函数或值覆盖属性。这是在如何解析 @declared_attr 的机制中的当前限制,并且如果检测到此条件,则会发出警告。此限制仅适用于 ORM 映射的列、关系和其他属性的MapperProperty风格。它适用于诸如 __tablename____mapper_args__ 等的声明性指令,这些指令在内部以与declared_attr.cascading不同的方式解析。

从多个混合类组合表/映射器参数

当使用声明性混合类指定 __table_args____mapper_args__ 时,您可能希望将一些参数从多个混合类中与您希望在类本身上定义的参数结合起来。可以在这里使用 declared_attr 装饰器来创建用户定义的排序例程,该例程从多个集合中提取:

代码语言:javascript复制
from sqlalchemy.orm import declarative_mixin, declared_attr

class MySQLSettings:
    __table_args__ = {"mysql_engine": "InnoDB"}

class MyOtherMixin:
    __table_args__ = {"info": "foo"}

class MyModel(MySQLSettings, MyOtherMixin, Base):
    __tablename__ = "my_model"

    @declared_attr.directive
    def __table_args__(cls):
        args = dict()
        args.update(MySQLSettings.__table_args__)
        args.update(MyOtherMixin.__table_args__)
        return args

    id = mapped_column(Integer, primary_key=True)

在混合类上使用命名约定创建索引和约束

对于具有命名约束的使用,如 IndexUniqueConstraintCheckConstraint,其中每个对象应该是唯一的,针对从混合类派生的特定表,需要为每个实际映射的类创建每个对象的单独实例。

作为一个简单的示例,要定义一个命名的、可能是多列的 Index,该索引适用于从混合类派生的所有表,可以使用 Index 的“内联”形式,并将其建立为 __table_args__ 的一部分,使用 declared_attr 来建立 __table_args__() 作为一个类方法,该方法将被每个子类调用:

代码语言:javascript复制
class MyMixin:
    a = mapped_column(Integer)
    b = mapped_column(Integer)

    @declared_attr.directive
    def __table_args__(cls):
        return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)

class MyModelA(MyMixin, Base):
    __tablename__ = "table_a"
    id = mapped_column(Integer, primary_key=True)

class MyModelB(MyMixin, Base):
    __tablename__ = "table_b"
    id = mapped_column(Integer, primary_key=True)

上面的示例将生成两个表 "table_a""table_b",其中包含索引 "test_idx_table_a""test_idx_table_b"

通常,在现代 SQLAlchemy 中,我们会使用一种命名约定,如在配置约束命名约定中记录的那样。虽然命名约定会在创建新的Constraint对象时自动进行,因为此约定是在基于特定Constraint的父Table的对象构造时间应用的,因此需要为每个继承子类创建一个不同的Constraint对象,并再次使用declared_attr__table_args__(),下面通过使用抽象映射基类进行说明:

代码语言:javascript复制
from uuid import UUID

from sqlalchemy import CheckConstraint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

constraint_naming_conventions = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

class Base(DeclarativeBase):
    metadata = MetaData(naming_convention=constraint_naming_conventions)

class MyAbstractBase(Base):
    __abstract__ = True

    @declared_attr.directive
    def __table_args__(cls):
        return (
            UniqueConstraint("uuid"),
            CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
        )

    id: Mapped[int] = mapped_column(primary_key=True)
    uuid: Mapped[UUID]
    x: Mapped[int]
    y: Mapped[int]

class ModelAlpha(MyAbstractBase):
    __tablename__ = "alpha"

class ModelBeta(MyAbstractBase):
    __tablename__ = "beta"

上述映射将生成包含所有约束的表特定名称的 DDL,包括主键、CHECK 约束、唯一约束:

代码语言:javascript复制
CREATE  TABLE  alpha  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_alpha  PRIMARY  KEY  (id),
  CONSTRAINT  uq_alpha_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_alpha_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

CREATE  TABLE  beta  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_beta  PRIMARY  KEY  (id),
  CONSTRAINT  uq_beta_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_beta_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

增强基类

除了使用纯混合外,本节中的大多数技术也可以直接应用于基类,用于适用于从特定基类派生的所有类的模式。下面的示例说明了如何在Base类方面应用上一节的一些示例:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
  """define a series of common elements that may be applied to mapped
 classes using this class as a base class."""

    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()

    __table_args__ = {"mysql_engine": "InnoDB"}
    __mapper_args__ = {"eager_defaults": True}

    id: Mapped[int] = mapped_column(primary_key=True)

class HasLogRecord:
  """mark classes that have a many-to-one relationship to the
 ``LogRecord`` class."""

    log_record_id: Mapped[int] = mapped_column(ForeignKey("logrecord.id"))

    @declared_attr
    def log_record(self) -> Mapped["LogRecord"]:
        return relationship("LogRecord")

class LogRecord(Base):
    log_info: Mapped[str]

class MyModel(HasLogRecord, Base):
    name: Mapped[str]

在上述示例中,MyModelLogRecord,在派生自Base时,它们的表名都将根据其类名派生,一个名为id的主键列,以及由Base.__table_args__Base.__mapper_args__定义的上述表和映射器参数。

在使用旧版declarative_base()registry.generate_base()时,可以使用declarative_base.cls参数来生成等效效果,如下所示的未注释示例:

代码语言:javascript复制
# legacy declarative_base() use

from sqlalchemy import Integer, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base:
  """define a series of common elements that may be applied to mapped
 classes using this class as a base class."""

    @declared_attr.directive
    def __tablename__(cls):
        return cls.__name__.lower()

    __table_args__ = {"mysql_engine": "InnoDB"}
    __mapper_args__ = {"eager_defaults": True}

    id = mapped_column(Integer, primary_key=True)

Base = declarative_base(cls=Base)

class HasLogRecord:
  """mark classes that have a many-to-one relationship to the
 ``LogRecord`` class."""

    log_record_id = mapped_column(ForeignKey("logrecord.id"))

    @declared_attr
    def log_record(self):
        return relationship("LogRecord")

class LogRecord(Base):
    log_info = mapped_column(String)

class MyModel(HasLogRecord, Base):
    name = mapped_column(String)

混合列

如果使用声明式表的配置风格(而不是命令式表配置),则可以在混合类中指示列,以便在声明式过程生成的 Table 的一部分。可以在声明式混合类中内联声明三种构造:mapped_column()MappedColumn

代码语言:javascript复制
class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(default=func.now())
    updated_at: Mapped[datetime]

class MyModel(TimestampMixin, Base):
    __tablename__ = "test"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]

在上述情况下,所有包括 TimestampMixin 在其类基类中的声明式类将自动包含一个名为 created_at 的列,该列对所有行插入应用时间戳,以及一个名为 updated_at 的列,该列不包含默认值以示例为目的(如果有的话,我们将使用 Column.onupdate 参数,该参数被 mapped_column() 接受)。这些列构造始终从源混合类或基类复制,因此可以将相同的混合类/基类应用于任意数量的目标类,每个目标类都将有自己的列构造。

所有声明式列形式都受到混合类的支持,包括:

带注释的属性 - 无论是否存在 mapped_column()

代码语言:javascript复制
class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(default=func.now())
    updated_at: Mapped[datetime]

mapped_column - 无论是否存在 Mapped

代码语言:javascript复制
class TimestampMixin:
    created_at = mapped_column(default=func.now())
    updated_at: Mapped[datetime] = mapped_column()

Column - 传统的声明式形式:

代码语言:javascript复制
class TimestampMixin:
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime)

在上述每种形式中,声明式通过创建构造的副本来处理混合类上的基于列的属性,然后将其应用于目标类。

版本 2.0 中的变化:声明式 API 现在可以接受 Column 对象以及任何形式的 mapped_column() 构造,当使用混合类时无需使用 declared_attr()。已经移除了以前的限制,这些限制阻止直接在混合类中使用具有 ForeignKey 元素的列。

混入关系

通过relationship()创建的关系通过declared_attr方法提供的声明式混合类,排除了在复制关系及其可能与列绑定的内容时可能出现的任何歧义。下面是一个示例,将外键列和关系组合在一起,以便两个类FooBar都可以配置为通过多对一引用一个共同的目标类:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

class Base(DeclarativeBase):
    pass

class RefTargetMixin:
    target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))

    @declared_attr
    def target(cls) -> Mapped["Target"]:
        return relationship("Target")

class Foo(RefTargetMixin, Base):
    __tablename__ = "foo"
    id: Mapped[int] = mapped_column(primary_key=True)

class Bar(RefTargetMixin, Base):
    __tablename__ = "bar"
    id: Mapped[int] = mapped_column(primary_key=True)

class Target(Base):
    __tablename__ = "target"
    id: Mapped[int] = mapped_column(primary_key=True)

使用上述映射,每个FooBar都包含一个到Target的关系,通过.target属性访问:

代码语言:javascript复制
>>> from sqlalchemy import select
>>> print(select(Foo).join(Foo.target))
SELECT  foo.id,  foo.target_id
FROM  foo  JOIN  target  ON  target.id  =  foo.target_id
>>> print(select(Bar).join(Bar.target))
SELECT  bar.id,  bar.target_id
FROM  bar  JOIN  target  ON  target.id  =  bar.target_id 

类似relationship.primaryjoin的特殊参数也可以在混入的 classmethod 中使用,这些参数通常需要引用正在映射的类。对于需要引用本地映射列的方案,在普通情况下,这些列通过 Declarative 作为映射类的属性提供,该类作为参数cls传递给修饰的 classmethod。使用此功能,我们可以例如使用显式的 primaryjoin 重写RefTargetMixin.target方法,该方法引用了Targetcls上的待定映射列:

代码语言:javascript复制
class Target(Base):
    __tablename__ = "target"
    id: Mapped[int] = mapped_column(primary_key=True)

class RefTargetMixin:
    target_id: Mapped[int] = mapped_column(ForeignKey("target.id"))

    @declared_attr
    def target(cls) -> Mapped["Target"]:
        # illustrates explicit 'primaryjoin' argument
        return relationship("Target", primaryjoin=Target.id == cls.target_id)

混合使用column_property()和其他MapperProperty

relationship()类似,其他MapperProperty子类,如column_property()在被混合使用时也需要生成类局部副本,因此在被declared_attr修饰的函数内声明。在该函数内,使用mapped_column()MappedColumn声明的其他普通映射列将从cls参数中提取,以便它们可以被用来组合新的属性,如下例所示,将两个列相加:

代码语言:javascript复制
from sqlalchemy.orm import column_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class SomethingMixin:
    x: Mapped[int]
    y: Mapped[int]

    @declared_attr
    def x_plus_y(cls) -> Mapped[int]:
        return column_property(cls.x   cls.y)

class Something(SomethingMixin, Base):
    __tablename__ = "something"

    id: Mapped[int] = mapped_column(primary_key=True)

在上面的例子中,我们可以在生成完整表达式的语句中使用Something.x_plus_y

代码语言:javascript复制
>>> from sqlalchemy import select
>>> print(select(Something.x_plus_y))
SELECT  something.x     something.y  AS  anon_1
FROM  something 

提示

declared_attr装饰器使装饰的可调用对象表现得完全像一个类方法。然而,像Pylance这样的类型工具可能无法识别这一点,这有时会导致它在函数体内部抱怨无法访问cls变量。当出现此问题时,可以直接将@classmethod装饰器与declared_attr结合使用来解决:

代码语言:javascript复制
class SomethingMixin:
    x: Mapped[int]
    y: Mapped[int]

    @declared_attr
    @classmethod
    def x_plus_y(cls) -> Mapped[int]:
        return column_property(cls.x   cls.y)

新版本 2.0 中:- declared_attr可以容纳使用@classmethod装饰的函数,以帮助需要的PEP 484集成。

使用混合类和基类与映射继承模式

在处理映射类继承层次结构中所记录的映射继承模式时,使用declared_attr时,无论是使用混合类还是在类层次结构中增加映射和非映射的超类时,都会存在一些额外的功能。

当在混合类或基类上定义由declared_attr装饰的函数,以便由映射继承层次结构中的子类解释时,有一个重要的区别是函数生成特殊名称(例如__tablename____mapper_args__)与生成普通映射属性(例如mapped_column()relationship())之间的区别。定义声明性指令的函数在层次结构中的每个子类中都会被调用,而生成映射属性的函数仅在层次结构中的第一个映射的超类中被调用

此行为差异的原理是映射属性已经可以被类继承,例如,超类映射表上的特定列不应该重复到子类中,而特定于特定类或其映射表的元素不可继承,例如本地映射的表名。

这两种用例之间行为上的差异在以下两个部分中得到展示。

使用带有继承TableMapper参数的declared_attr()

使用混合类的常见方法是创建一个def __tablename__(cls)函数,动态生成映射的Table的名称。

这个配方可用于为继承映射器层次结构生成表名,如下例所示,该示例创建了一个混合类,根据类名为每个类提供一个简单的表名。下面的示例说明了这个配方,在这个示例中为Person映射类和Person的子类Engineer生成了一个表名,但没有为Person的子类Manager生成表名:

代码语言:javascript复制
from typing import Optional

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
  """override __tablename__ so that Manager is single-inheritance to Person"""

        return None

    __mapper_args__ = {"polymorphic_identity": "manager"}

在上面的示例中,Person基类以及Engineer类,作为生成新表名的Tablename混合类的子类,都将有一个生成的__tablename__属性,这对于 Declarative 表示每个类应该有自己的Table生成并映射到它。对于Engineer子类,应用的继承风格是联接表继承,因为它将映射到一个与基本person表连接的engineer表。从Person继承的任何其他子类也将默认应用这种继承风格(在这个特定示例中,需要为每个子类指定一个主键列;在下一节中会详细介绍)。

相比之下,Person的子类Manager 覆盖__tablename__类方法以返回None。这告诉 Declarative 这个类 不应 生成一个Table,而应该完全使用Person映射到的基本Table。对于Manager子类,应用的继承风格是单表继承。

上面的示例说明了 Declarative 指令如__tablename__必须分别应用于每个子类,因为每个映射类都需要声明将映射到哪个Table,或者是否将自身映射到继承的超类的Table

如果我们希望反转上述默认表方案,使单表继承成为默认情况,并且只有在提供__tablename__指令以覆盖它时才能定义联接表继承,则可以在顶层__tablename__()方法中使用 Declarative 辅助函数,在本例中是一个称为has_inherited_table()的辅助函数。如果超类已经映射到Table,此函数将返回True。我们可以在最基本的__tablename__()类方法中使用此辅助函数,以便在表已存在时有条件地返回None作为表名,从而默认情况下通过继承子类进行单表继承:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls):
        if has_inherited_table(cls):
            return None
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    @declared_attr.directive
    def __tablename__(cls):
  """override __tablename__ so that Engineer is joined-inheritance to Person"""

        return cls.__name__.lower()

    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    __mapper_args__ = {"polymorphic_identity": "manager"}
使用declared_attr()生成特定表继承列

与在使用declared_attr时处理__tablename__和其他特殊名称的方式相反,当我们混合列和属性(例如关系、列属性等)时,该函数仅在层次结构中的基类上调用,除非在与declared_attr.cascading子指令结合使用时使用declared_attr指令。在下面的示例中,只有Person类将接收一个名为id的列;对于未给出主键的Engineer,映射将失败:

代码语言:javascript复制
class HasId:
    id: Mapped[int] = mapped_column(primary_key=True)

class Person(HasId, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

# this mapping will fail, as there's no primary key
class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

在联接表继承中,通常情况下我们希望每个子类上都有不同命名的列。然而,在这种情况下,我们可能希望在每个表上都有一个id列,并通过外键引用它们。我们可以通过使用declared_attr.cascading修饰符作为混合来实现这一点,该修饰符指示该函数应该以几乎(请参阅下面的警告)与__tablename__相同的方式为层次结构中的每个类调用:

代码语言:javascript复制
class HasIdMixin:
    @declared_attr.cascading
    def id(cls) -> Mapped[int]:
        if has_inherited_table(cls):
            return mapped_column(ForeignKey("person.id"), primary_key=True)
        else:
            return mapped_column(Integer, primary_key=True)

class Person(HasIdMixin, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

警告

declared_attr.cascading 特性目前不允许子类使用不同的函数或值覆盖属性。这是在解析@declared_attr的机制中的当前限制,并且如果检测到此条件,则会发出警告。这个限制仅适用于 ORM 映射的列、关系和其他MapperProperty风格的属性。它不适用于声明性指令,如__tablename____mapper_args__等,在内部的解析方式与declared_attr.cascading不同。

使用 declared_attr() 与继承的 TableMapper 参数

使用 mixin 的一个常见方法是创建一个def __tablename__(cls)函数,该函数动态生成映射的 Table 名称。

此示例可用于为继承的映射器层次结构生成表名,如下例所示,它创建了一个基于类名的简单表名的 mixin。下面的示例说明了该示例,其中为Person映射类和PersonEngineer子类生成了一个表名,但未为PersonManager子类生成表名:

代码语言:javascript复制
from typing import Optional

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    @declared_attr.directive
    def __tablename__(cls) -> Optional[str]:
  """override __tablename__ so that Manager is single-inheritance to Person"""

        return None

    __mapper_args__ = {"polymorphic_identity": "manager"}

在上面的示例中,Person基类和Engineer类都是Tablename mixin 类的子类,该类生成新的表名,因此它们都会有一个生成的__tablename__属性,对于声明性来说,这表示每个类都应该有自己的 Table 生成,并且将映射到该表。对于Engineer子类,应用的继承风格是联接表继承,因为它将映射到一个连接到基本person表的表engineer。从Person继承的任何其他子类也将默认应用此继承风格(并且在这个特定示例中,每个子类都需要指定一个主键列;更多关于这一点的内容将在下一节中介绍)。

相比之下,PersonManager子类覆盖__tablename__类方法以返回None。这表明对于 Declarative 来说,这个类应该生成一个Table,而是完全使用Person被映射到的基础Table。对于Manager子类,应用的继承样式是单表继承。

上面的示例说明 Declarative 指令如__tablename__必须分别应用于每个子类,因为每个映射类都需要说明它将映射到哪个Table,或者它将自行映射到继承超类的Table

如果我们希望反转上面示例中的默认表方案,使得单表继承成为默认,并且只有在提供了__tablename__指令来覆盖它时才能定义连接表继承,我们可以在最顶层的__tablename__()方法中使用 Declarative 助手,本例中称为has_inherited_table()。此函数将在超类已经映射到Table时返回True。我们可以在最基本的__tablename__()类方法中使用此助手,以便我们可以在表已经存在时有条件地返回None作为表名,从而默认指示继承子类的单表继承:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Tablename:
    @declared_attr.directive
    def __tablename__(cls):
        if has_inherited_table(cls):
            return None
        return cls.__name__.lower()

class Person(Tablename, Base):
    id: Mapped[int] = mapped_column(primary_key=True)
    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    @declared_attr.directive
    def __tablename__(cls):
  """override __tablename__ so that Engineer is joined-inheritance to Person"""

        return cls.__name__.lower()

    id: Mapped[int] = mapped_column(ForeignKey("person.id"), primary_key=True)

    primary_language: Mapped[str]

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class Manager(Person):
    __mapper_args__ = {"polymorphic_identity": "manager"}
使用declared_attr()生成特定于表的继承列

与在使用declared_attr时处理__tablename__和其他特殊名称的方式相反,当我们混合列和属性(例如关系、列属性等)时,该函数仅在层次结构中的基类中调用,除非与declared_attr.cascading子指令结合使用declared_attr指令。下面,只有Person类将收到名为id的列;对于未给出主键的Engineer,映射将失败:

代码语言:javascript复制
class HasId:
    id: Mapped[int] = mapped_column(primary_key=True)

class Person(HasId, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

# this mapping will fail, as there's no primary key
class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

在连接表继承中通常情况下,我们希望每个子类都有具有不同名称的列。但在这种情况下,我们可能希望在每个表上都有一个id列,并且通过外键相互引用。我们可以通过使用declared_attr.cascading修饰符作为混入来实现此目的,该修饰符指示该函数应在层次结构中的每个类中调用,与__tablename__几乎(参见下面的警告)相同的方式:

代码语言:javascript复制
class HasIdMixin:
    @declared_attr.cascading
    def id(cls) -> Mapped[int]:
        if has_inherited_table(cls):
            return mapped_column(ForeignKey("person.id"), primary_key=True)
        else:
            return mapped_column(Integer, primary_key=True)

class Person(HasIdMixin, Base):
    __tablename__ = "person"

    discriminator: Mapped[str]
    __mapper_args__ = {"polymorphic_on": "discriminator"}

class Engineer(Person):
    __tablename__ = "engineer"

    primary_language: Mapped[str]
    __mapper_args__ = {"polymorphic_identity": "engineer"}

警告

declared_attr.cascading 特性目前允许子类使用不同的函数或值来覆盖属性。这是在解析@declared_attr时当前机制的限制,并且如果检测到此条件,则会发出警告。此限制仅适用于 ORM 映射的列、关联和其他MapperProperty风格的属性。它适用于诸如__tablename____mapper_args__等的声明性指令,后者在内部解析方式与declared_attr.cascading不同。

将来自多个混入的表/映射器参数组合起来

在使用声明性混入指定的__table_args____mapper_args__的情况下,您可能希望将几个混入的一些参数与您希望在类本身上定义的参数合并。在这里可以使用declared_attr装饰器来创建用户定义的排序例程,这些例程来自多个集合:

代码语言:javascript复制
from sqlalchemy.orm import declarative_mixin, declared_attr

class MySQLSettings:
    __table_args__ = {"mysql_engine": "InnoDB"}

class MyOtherMixin:
    __table_args__ = {"info": "foo"}

class MyModel(MySQLSettings, MyOtherMixin, Base):
    __tablename__ = "my_model"

    @declared_attr.directive
    def __table_args__(cls):
        args = dict()
        args.update(MySQLSettings.__table_args__)
        args.update(MyOtherMixin.__table_args__)
        return args

    id = mapped_column(Integer, primary_key=True)

使用混入创建具有命名约定的索引和约束

使用命名约束,如IndexUniqueConstraintCheckConstraint,其中每个对象应该是从混入派生的特定表上唯一的,需要为每个实际映射类创建每个对象的单个实例。

作为一个简单的例子,要定义一个命名的、可能是多列的Index,该索引适用于从混合类派生的所有表,可以使用Index的“内联”形式,并将其建立为__table_args__的一部分,使用declared_attr来建立__table_args__()作为一个类方法,该方法将被调用用于每个子类:

代码语言:javascript复制
class MyMixin:
    a = mapped_column(Integer)
    b = mapped_column(Integer)

    @declared_attr.directive
    def __table_args__(cls):
        return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)

class MyModelA(MyMixin, Base):
    __tablename__ = "table_a"
    id = mapped_column(Integer, primary_key=True)

class MyModelB(MyMixin, Base):
    __tablename__ = "table_b"
    id = mapped_column(Integer, primary_key=True)

上面的例子将生成两个表"table_a""table_b",带有索引"test_idx_table_a""test_idx_table_b"

通常,在现代 SQLAlchemy 中,我们会使用命名约定,如配置约束命名约定中所述。虽然命名约定在创建新的Constraint对象时会自动进行,因为该约定是根据特定的父Table在对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时时的,需要为每个继承子类创建一个独立的Constraint对象,并再次使用declared_attr__table_args__(),下面以抽象映射基类进行说明:

代码语言:javascript复制
from uuid import UUID

from sqlalchemy import CheckConstraint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

constraint_naming_conventions = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

class Base(DeclarativeBase):
    metadata = MetaData(naming_convention=constraint_naming_conventions)

class MyAbstractBase(Base):
    __abstract__ = True

    @declared_attr.directive
    def __table_args__(cls):
        return (
            UniqueConstraint("uuid"),
            CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
        )

    id: Mapped[int] = mapped_column(primary_key=True)
    uuid: Mapped[UUID]
    x: Mapped[int]
    y: Mapped[int]

class ModelAlpha(MyAbstractBase):
    __tablename__ = "alpha"

class ModelBeta(MyAbstractBase):
    __tablename__ = "beta"

上述映射将生成包括所有约束的特定于表的名称的 DDL,包括主键、CHECK 约束、唯一约束:

代码语言:javascript复制
CREATE  TABLE  alpha  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_alpha  PRIMARY  KEY  (id),
  CONSTRAINT  uq_alpha_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_alpha_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

CREATE  TABLE  beta  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_beta  PRIMARY  KEY  (id),
  CONSTRAINT  uq_beta_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_beta_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

用命名约定,如配置约束命名约定中所述。虽然命名约定在创建新的Constraint对象时会自动进行,因为该约定是根据特定的父Table在对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时应用于对象构建时时的,需要为每个继承子类创建一个独立的Constraint对象,并再次使用declared_attr__table_args__(),下面以抽象映射基类进行说明:

代码语言:javascript复制
from uuid import UUID

from sqlalchemy import CheckConstraint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

constraint_naming_conventions = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

class Base(DeclarativeBase):
    metadata = MetaData(naming_convention=constraint_naming_conventions)

class MyAbstractBase(Base):
    __abstract__ = True

    @declared_attr.directive
    def __table_args__(cls):
        return (
            UniqueConstraint("uuid"),
            CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
        )

    id: Mapped[int] = mapped_column(primary_key=True)
    uuid: Mapped[UUID]
    x: Mapped[int]
    y: Mapped[int]

class ModelAlpha(MyAbstractBase):
    __tablename__ = "alpha"

class ModelBeta(MyAbstractBase):
    __tablename__ = "beta"

上述映射将生成包括所有约束的特定于表的名称的 DDL,包括主键、CHECK 约束、唯一约束:

代码语言:javascript复制
CREATE  TABLE  alpha  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_alpha  PRIMARY  KEY  (id),
  CONSTRAINT  uq_alpha_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_alpha_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

CREATE  TABLE  beta  (
  id  INTEGER  NOT  NULL,
  uuid  CHAR(32)  NOT  NULL,
  x  INTEGER  NOT  NULL,
  y  INTEGER  NOT  NULL,
  CONSTRAINT  pk_beta  PRIMARY  KEY  (id),
  CONSTRAINT  uq_beta_uuid  UNIQUE  (uuid),
  CONSTRAINT  ck_beta_xy_chk  CHECK  (x  >  0  OR  y  <  100)
)

0 人点赞