使用 Mixin 构建映射层次结构

使用 Declarative 风格映射类时,一个常见需求是共享通用功能,例如特定列、表或 Mapper 选项、命名方案或其他映射属性,在许多类之间。当使用声明式映射时,此惯例通过使用 mixin 类 以及增强声明式基类本身来支持。

提示

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

下面是一些常用混合惯例的示例

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,它在基类中包含两个 mixin CommonMixinHasLogRecord,以及一个补充类 LogRecord,它也包含 CommonMixin,演示了 mixin 和基类上支持的各种结构,包括

  • 使用 mapped_column()MappedColumn 声明的列将从 mixin 或基类复制到要映射的目标类;上面通过列属性 CommonMixin.idHasLogRecord.log_record_id 说明了这一点。

  • 声明式指令,例如 __table_args____mapper_args__ 可以分配给 mixin 或基类,它们将自动生效,适用于继承自该 mixin 或基类的任何类。上面的示例使用 __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() 示例来说明

>>> 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 类型工具有意义,因为它会在创建方法来覆盖声明式指令(如 __tablename____mapper_args____table_args__)时调整预期返回值类型。

新版功能: 作为 SQLAlchemy ORM 对 PEP 484 类型支持的一部分,在 declared_attr 中添加了 declared_attr.directive,用于区分 Mapped 属性和声明式配置属性。

混入和基类的顺序没有固定的约定。普通的 Python 方法解析规则适用,上面的示例在使用以下顺序时也能正常工作。

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

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

提示

虽然上面的示例使用的是基于 Mapped 注释类的 带注释的声明式表 形式,但混入类也能与非注释和遗留的声明式形式完美兼容,例如在使用 Column 而不是 mapped_column() 时。

更改内容: 对于来自 SQLAlchemy 1.4 系列的用户,他们可能一直在使用 mypy 插件,不再需要 declarative_mixin() 类装饰器来标记声明式混入,假设不再使用 mypy 插件。

扩展 Base

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

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 参数生成等效效果,如以下非注释示例中所示。

# 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 结构都可以在声明式混入中内联声明。

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()

    class TimestampMixin:
        created_at: Mapped[datetime] = mapped_column(default=func.now())
        updated_at: Mapped[datetime]
  • mapped_column - 无论是否包含 Mapped

    class TimestampMixin:
        created_at = mapped_column(default=func.now())
        updated_at: Mapped[datetime] = mapped_column()
  • Column - 遗留的声明式形式

    class TimestampMixin:
        created_at = Column(DateTime, default=func.now())
        updated_at = Column(DateTime)

在以上每种形式中,声明式都会通过创建副本来处理混入类中的基于列的属性,然后将该副本应用于目标类。

更改内容: 声明式 API 现在可以容纳 Column 对象以及在使用混入时无需使用 declared_attr() 的任何形式的 mapped_column() 结构。之前阻止包含 ForeignKey 元素的列直接在混入中使用的问题已经解决。

混入关系

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

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 属性访问。

>>> 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 之类的特殊参数也可以在混入的类方法中使用,这些方法通常需要引用正在映射的类。对于需要引用本地映射列的方案,在普通情况下,声明式会将这些列作为映射类上的属性提供,该映射类作为 cls 参数传递给装饰的类方法。使用此功能,例如,我们可以使用显式的 primaryjoin 重新编写 RefTargetMixin.target 方法,该 primaryjoin 引用 Targetcls 上待定的映射列。

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 参数中获得,以便它们可以用于组合新的属性,如以下示例中将两个列加在一起。

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

>>> 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 组合在一起,如

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())的函数之间存在重要区别。定义声明式指令的函数将对层次结构中的每个子类调用,而生成映射属性的函数将仅对层次结构中的第一个映射超类调用

这种行为差异的理由是,映射属性已经是可继承的,例如超类映射表上的特定列不应该复制到子类上,而特定于特定类或其映射表的元素是不可继承的,例如本地映射的表的名称。

以下两节展示了这两种用例之间的行为差异。

使用 declared_attr() 继承 TableMapper 参数

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

这种方法可用于为继承的映射器层次结构生成表名,如以下示例所示,该示例创建一个混合类,为每个类提供一个基于类名的简单表名。以下示例说明了该方法,该方法为 Person 映射类和 Person 的子类 Engineer 生成表名,但不会为 Person 的子类 Manager 生成表名。

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__ 属性,这对声明式表示每个类都应该有自己的 Table 生成,该类将被映射到该表。对于 Engineer 子类,应用的继承样式是 连接表继承,因为它将被映射到一个表 engineer,该表连接到基表 person。从 Person 继承的任何其他子类也都将默认应用这种继承样式(在本示例中,每个子类都需要指定一个主键列;下一节将详细介绍)。

相比之下,Person 的子类 Manager覆盖 __tablename__ 类方法以返回 None。这表示声明式该类不应该生成一个 Table,而是将专门使用映射到 Person 的基 Table。对于 Manager 子类,应用的继承样式是 单表继承

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

如果我们想反转上面示例中说明的默认表模式,以便单表继承成为默认模式,而连接表继承只有在提供 __tablename__ 指令以覆盖它时才定义,我们可以利用声明式助手在最顶层的 __tablename__() 方法中使用,在这种情况下,助手称为 has_inherited_table()。如果超类已经被映射到一个 Table,这个函数将返回 True。我们可以在最底层的 __tablename__() 类方法中使用这个助手,以便我们可以在存在表时有条件地返回 None 作为表名,从而指示默认情况下对继承的子类使用单表继承。

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 上失败,因为没有提供主键。

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__ 的调用方式几乎(参见下面的警告)相同。

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

组合来自多个 Mixin 的 Table/Mapper 参数

对于使用声明性 Mixin 指定的 __table_args____mapper_args__,您可能希望将来自多个 Mixin 的一些参数与您希望在类本身定义的参数组合在一起。可以使用 declared_attr 装饰器在这里创建用户定义的整理例程,这些例程从多个集合中提取。

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)

在 Mixin 上使用命名约定创建索引和约束

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

作为一个简单的例子,要定义一个命名的、可能包含多列的 Index,该索引应用于从 Mixin 派生的所有表,请使用 Index 的“内联”形式,并将其作为 __table_args__ 的一部分建立,使用 declared_attr__table_args__() 建立为一个类方法,该方法将为每个子类调用

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 中,我们会使用命名约定,如 配置约束命名约定 中所述。虽然命名约定会使用 MetaData.naming_convention 自动进行,当创建新的 Constraint 对象时,由于此约定是在基于特定 Constraint 的父 Table 的对象构造时应用的,因此需要为每个继承的子类创建一个单独的 Constraint 对象,并使用自己的 Table,同样使用 declared_attr__table_args__(),下面以抽象映射基类为例进行说明

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 约束、唯一约束

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)
)