使用 Mixins 组合映射层级结构

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

提示

除了 mixin 类之外,常见的列选项也可以在使用 PEP 593 Annotated 类型在多个类之间共享;请参阅 将多种类型配置映射到 Python 类型将整个列声明映射到 Python 类型,以了解这些 SQLAlchemy 2.0 特性的背景信息。

下面是一些常用的混入习惯用法的示例

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__ 属性说明了这一点。

  • 所有声明式指令,包括 __tablename____table____table_args____mapper_args__,都可以使用用户定义的类方法来实现,这些类方法使用 declared_attr 装饰器(特别是 declared_attr.directive 子成员,稍后会详细介绍)。上面,这使用 def __tablename__(cls) 类方法来说明,该方法动态生成 Table 名称;当应用于 MyModel 类时,表名将生成为 "mymodel",而当应用于 LogRecord 类时,表名将生成为 "logrecord"

  • 其他 ORM 属性,例如 relationship(),也可以在要映射的目标类上生成,使用同样由 declared_attr 装饰器修饰的用户定义的类方法。上面,这通过生成到名为 LogRecord 的映射对象的多对一 relationship() 来说明。

以上功能都可以使用 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 函数使用注解是完全可选的,并且不被声明式解析;但是,为了通过 Mypy --strict 类型检查,需要这些注解。

此外,上面说明的 declared_attr.directive 子成员也是可选的,并且仅对于 PEP 484 类型工具很重要,因为它在创建用于覆盖声明式指令(如 __tablename____mapper_args____table_args__)的方法时调整了预期的返回类型。

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

mixin 和基类的顺序没有固定的约定。应用普通的 Python 方法解析规则,上面的示例也可以像这样工作

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

这样做是因为这里的 Base 没有定义 CommonMixinHasLogRecord 定义的任何变量,即 __tablename____table_args__id 等。如果 Base 定义了同名的属性,则继承列表中首先放置的类将确定新定义的类上使用的属性。

提示

虽然上面的示例使用基于 Mapped 注解类的 注解声明式表 形式,但 mixin 类也与非注解和旧式声明式形式完美配合,例如在使用 Column 而不是 mapped_column() 时。

2.0 版本更改: 对于来自 SQLAlchemy 1.4 系列并且可能一直在使用 mypy 插件 的用户,不再需要 declarative_mixin() 类装饰器来标记声明式 mixin,假设不再使用 mypy 插件。

增强基类

除了使用纯 mixin 之外,本节中的大多数技术也可以直接应用于基类,对于应应用于从特定基类派生的所有类的模式。下面的示例说明了上一节中的一些示例,以 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]

在上面,MyModel 以及 LogRecord,在从 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)

混入列

可以在 mixin 中指示列,假设正在使用 声明式表 风格的配置(而不是 命令式表 配置),这样在 mixin 上声明的列就可以被复制,成为声明式过程生成的 Table 的一部分。mapped_column()MappedColumn 这三种构造都可以在声明式 mixin 中内联声明

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() 接受)。这些列构造始终从原始 mixin 或基类复制,以便相同的 mixin/基类可以应用于任意数量的目标类,每个目标类都将具有自己的列构造。

Mixin 支持所有声明式列形式,包括

  • 注解属性 - 无论是否包含 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)

在上述每种形式中,声明式处理 mixin 类上的基于列的属性,方法是创建构造的副本,然后将其应用于目标类。

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

混入关系

relationship() 创建的关系专门使用 declared_attr 方法提供给声明式 mixin 类,从而消除了复制关系及其可能绑定列的内容时可能出现的任何歧义。下面是一个示例,它组合了外键列和关系,以便可以将 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())也需要在 mixin 使用时生成类本地副本,因此也在由 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 集成。

在映射继承模式中使用 Mixins 和基类

当处理 映射类继承层级结构 中记录的映射器继承模式时,当在 mixin 类中使用 declared_attr 时,或者当增强类层次结构中的映射和非映射超类时,存在一些额外的功能。

当在 mixin 类或基类上定义由 declared_attr 装饰的函数以供映射继承层次结构中的子类解释时,在生成声明式使用的特殊名称(如 __tablename____mapper_args__)的函数与可能生成普通映射属性(如 mapped_column()relationship())的函数之间存在重要的区别。定义声明式指令的函数为层次结构中的每个子类调用,而生成映射属性的函数仅为层次结构中的第一个映射超类调用

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

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

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

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

此做法可用于为继承的映射器层次结构生成表名,如下面的示例所示,该示例创建一个 mixin,为每个类提供基于类名的简单表名。下面的示例说明了为 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 mixin 类的子类)都将具有生成的 __tablename__ 属性,这向声明式指示每个类都应具有其自己的 Table 生成并映射到该表。Engineer 子类应用的继承风格是 连接表继承,因为它将映射到连接到基本 person 表的 engineer 表。从 Person 继承的任何其他子类也将默认应用此继承风格(并且在此特定示例中,将需要各自指定主键列;更多内容请参见下一节)。

相比之下,Manager 子类 Person 重写__tablename__ 类方法以返回 None。 这向 Declarative 表明,这个类不应生成 Table,而是将完全使用 Table 基类,Person 类映射到该基类。 对于 Manager 子类,应用的继承样式是 单表继承

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

如果我们想要反转上面说明的默认表方案,以便单表继承成为默认设置,并且只有在为 __tablename__ 指令提供覆盖时才能定义连接表继承,我们可以在最顶层的 __tablename__() 方法中使用 Declarative 助手,在本例中是一个名为 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() 生成特定于表的继承列

__tablename__ 和其他特殊名称在使用 declared_attr 时的处理方式相反,当我们混合使用列和属性(例如,关系、列属性等)时,除非将 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 样式的属性。 它适用于 Declarative 指令,例如 __tablename____mapper_args__ 等,这些指令的内部解析方式与 declared_attr.cascading 不同。

合并来自多个 Mixin 的表/映射器参数

在声明性 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 的父 Table 应用,但是需要为每个继承子类创建不同的 Constraint 对象,每个子类都有自己的 Table,再次使用带有 __table_args__()declared_attr,如下所示,使用抽象映射基类进行说明。

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