使用声明式配置表

声明式映射 中介绍的那样,声明式风格包括在同一时间生成映射的 Table 对象的能力,或者直接使用 Table 或其他 FromClause 对象。

以下示例假设声明式基类为

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass

以下所有示例都演示了一个从上述 Base 继承的类。在 使用装饰器的声明式映射 (无声明式基类) 中介绍的装饰器风格与所有以下示例完全兼容,同样兼容声明式基类的旧形式,包括由 declarative_base() 生成的基类。

使用 mapped_column() 的声明式表

使用声明式时,要映射的类的主体通常包含一个名为 __tablename__ 的属性,该属性指示要生成的 Table 的字符串名称。然后,在类主体中使用 mapped_column() 结构(它具有普通 Column 类中没有的额外 ORM 特定配置功能),来指示表中的列。以下示例演示了在声明式映射中使用此结构的最基本用法

from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50), nullable=False)
    fullname = mapped_column(String)
    nickname = mapped_column(String(30))

上面,mapped_column() 结构在类定义中作为类级别属性内联放置。在声明类的时刻,声明式映射过程将针对与声明式 Base 关联的 MetaData 集合生成一个新的 Table 对象;然后,每个 mapped_column() 实例将在此过程中用于生成一个 Column 对象,该对象将成为此 Table 对象的 Table.columns 集合的一部分。

在上面的示例中,声明式将构建一个 Table 结构,等效于以下代码

# equivalent Table object produced
user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("fullname", String()),
    Column("nickname", String(30)),
)

当上面的 User 类被映射时,可以通过 __table__ 属性直接访问此 Table 对象;这将在 访问表和元数据 中进一步描述。

``mapped_column()`` 构造函数接受 ``Column`` 构造函数接受的所有参数,以及一些额外的 ORM 特定参数。``mapped_column.__name`` 字段,表示数据库列的名称,通常被省略,因为声明式过程将使用赋予构造函数的属性名称并将此作为列的名称(在上面的示例中,这指的是名称 ``id``、``name``、``fullname``、``nickname``)。分配备用 ``mapped_column.__name`` 也是有效的,此时生成的 ``Column`` 将在 SQL 和 DDL 语句中使用给定的名称,而 ``User`` 映射类将继续允许使用给定的属性名称访问属性,而与赋予列本身的名称无关(有关此方面的更多信息,请参阅 显式命名声明式映射列)。

提示

``mapped_column()`` 构造函数 **仅在声明式类映射中有效**。在使用 Core 构造 ``Table`` 对象以及使用 命令式表 配置时,仍然需要 ``Column`` 构造函数来指示数据库列的存在。

另请参阅

映射表列 - 包含关于影响 ``Mapper`` 如何解释传入的 ``Column`` 对象的额外说明。

使用带注释的声明式表(``mapped_column()`` 的类型注释形式)

``mapped_column()`` 构造函数能够从 PEP 484 类型注释中推导出其列配置信息,这些类型注释与声明式映射类中声明的属性相关联。这些类型注释,如果使用,**必须**存在于名为 ``Mapped`` 的特殊 SQLAlchemy 类型中,它是一个 泛型 类型,然后在其中指示特定的 Python 类型。

以下是上一节中映射的示例,其中添加了 ``Mapped`` 的使用。

from typing import Optional

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(30))

在上面,当声明式处理每个类属性时,每个 ``mapped_column()`` 将从左侧对应的 ``Mapped`` 类型注释(如果存在)中推导出其他参数。此外,声明式将隐式生成一个空的 ``mapped_column()`` 指令,只要遇到没有为属性分配值的 ``Mapped`` 类型注释(此形式的灵感来自 Python 数据类 中使用的类似样式);此 ``mapped_column()`` 构造函数继续从现有的 ``Mapped`` 注释中推导出其配置。

``mapped_column()`` 从 ``Mapped`` 注释中推导出数据类型和可空性

``mapped_column()`` 从 ``Mapped`` 注释中推导出的两个属性是

  • **数据类型** - ``Mapped`` 内部给出的 Python 类型,如果存在,包含在 ``typing.Optional`` 构造中,与 ``TypeEngine`` 子类关联,例如 ``Integer``、``String``、``DateTime`` 或 ``Uuid``,仅举几个常见的类型。

    数据类型根据 Python 类型到 SQLAlchemy 数据类型的字典来确定。此字典是完全可定制的,如下一节 自定义类型映射 中所述。默认类型映射的实现方式如下面的代码示例所示。

    from typing import Any
    from typing import Dict
    from typing import Type
    
    import datetime
    import decimal
    import uuid
    
    from sqlalchemy import types
    
    # default type mapping, deriving the type for mapped_column()
    # from a Mapped[] annotation
    type_map: Dict[Type[Any], TypeEngine[Any]] = {
        bool: types.Boolean(),
        bytes: types.LargeBinary(),
        datetime.date: types.Date(),
        datetime.datetime: types.DateTime(),
        datetime.time: types.Time(),
        datetime.timedelta: types.Interval(),
        decimal.Decimal: types.Numeric(),
        float: types.Float(),
        int: types.Integer(),
        str: types.String(),
        uuid.UUID: types.Uuid(),
    }

    如果 ``mapped_column()`` 构造函数指示作为传递给 ``mapped_column.__type`` 参数的显式类型,则会忽略给定的 Python 类型。

  • **可空性** - ``mapped_column()`` 构造函数将首先通过 ``mapped_column.nullable`` 参数(以 ``True`` 或 ``False`` 形式传递)来指示其 ``Column`` 是 ``NULL`` 还是 ``NOT NULL``。此外,如果 ``mapped_column.primary_key`` 参数存在并设置为 ``True``,那也将意味着该列应为 ``NOT NULL``。

    如果缺少 **两个** 参数,则会使用 typing.Optional[]Mapped 类型注释中的存在来确定可空性,其中 typing.Optional[] 表示 NULL,而缺少 typing.Optional[] 表示 NOT NULL。如果根本没有 Mapped[] 注释,并且没有 mapped_column.nullablemapped_column.primary_key 参数,则使用 SQLAlchemy 通常对 Column 的默认值 NULL

    在下面的示例中,iddata 列将为 NOT NULL,而 additional_info 列将为 NULL

    from typing import Optional
    
    from sqlalchemy.orm import DeclarativeBase
    from sqlalchemy.orm import Mapped
    from sqlalchemy.orm import mapped_column
    
    
    class Base(DeclarativeBase):
        pass
    
    
    class SomeClass(Base):
        __tablename__ = "some_table"
    
        # primary_key=True, therefore will be NOT NULL
        id: Mapped[int] = mapped_column(primary_key=True)
    
        # not Optional[], therefore will be NOT NULL
        data: Mapped[str]
    
        # Optional[], therefore will be NULL
        additional_info: Mapped[Optional[str]]

    拥有 mapped_column() 且其可空性与注释所隐含的可空性 **不同** 也是完全有效的。例如,ORM 映射属性可以被注释为允许在 Python 代码中使用 None,该代码在对象首次被创建和填充时使用该对象,但该值最终将被写入到数据库列中,该列为 NOT NULL。当存在 mapped_column.nullable 参数时,它将始终优先。

    class SomeClass(Base):
        # ...
    
        # will be String() NOT NULL, but can be None in Python
        data: Mapped[Optional[str]] = mapped_column(nullable=False)

    类似地,非 None 属性写入到数据库列中,由于某种原因需要在模式级别为 NULL,可以将 mapped_column.nullable 设置为 True

    class SomeClass(Base):
        # ...
    
        # will be String() NULL, but type checker will not expect
        # the attribute to be None
        data: Mapped[str] = mapped_column(nullable=True)

自定义类型映射

上一节中描述的 Python 类型到 SQLAlchemy TypeEngine 类型的映射默认使用 sqlalchemy.sql.sqltypes 模块中存在的硬编码字典。但是,协调声明式映射过程的 registry 对象将首先查询本地用户定义的类型字典,该字典可以作为 registry.type_annotation_map 参数在构造 registry 时传递,该字典可以与 DeclarativeBase 超类在首次使用时关联。

例如,如果我们希望对 int 使用 BIGINT 数据类型,对 datetime.datetime 使用带有 timezone=TrueTIMESTAMP 数据类型,然后仅在 Microsoft SQL Server 上,我们希望在使用 Python str 时使用 NVARCHAR 数据类型,则可以将注册表和声明式基类配置为

import datetime

from sqlalchemy import BIGINT, Integer, NVARCHAR, String, TIMESTAMP
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped, mapped_column, registry


class Base(DeclarativeBase):
    type_annotation_map = {
        int: BIGINT,
        datetime.datetime: TIMESTAMP(timezone=True),
        str: String().with_variant(NVARCHAR, "mssql"),
    }


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    date: Mapped[datetime.datetime]
    status: Mapped[str]

下面演示了为上述映射生成的 CREATE TABLE 语句,首先是在 Microsoft SQL Server 后端,演示了 NVARCHAR 数据类型

>>> from sqlalchemy.schema import CreateTable
>>> from sqlalchemy.dialects import mssql, postgresql
>>> print(CreateTable(SomeClass.__table__).compile(dialect=mssql.dialect()))
CREATE TABLE some_table ( id BIGINT NOT NULL IDENTITY, date TIMESTAMP NOT NULL, status NVARCHAR(max) NOT NULL, PRIMARY KEY (id) )

然后是在 PostgreSQL 后端,演示了 TIMESTAMP WITH TIME ZONE

>>> print(CreateTable(SomeClass.__table__).compile(dialect=postgresql.dialect()))
CREATE TABLE some_table ( id BIGSERIAL NOT NULL, date TIMESTAMP WITH TIME ZONE NOT NULL, status VARCHAR NOT NULL, PRIMARY KEY (id) )

通过使用 TypeEngine.with_variant() 等方法,我们可以构建一个自定义的类型映射,以满足不同后端的需求,同时仍然能够使用简洁的仅注释的 mapped_column() 配置。除了这种方法之外,还有另外两个级别的 Python 类型可配置性,将在接下来的两节中介绍。

将多个类型配置映射到 Python 类型

由于可以使用 registry.type_annotation_map 参数将单个 Python 类型与任何类型的 TypeEngine 配置相关联,因此还有一个附加功能,即能够将单个 Python 类型与基于附加类型限定符的 SQL 类型不同变体相关联。一个典型的示例是将 Python str 数据类型映射到不同长度的 VARCHAR SQL 类型。另一个示例是将不同类型的 decimal.Decimal 映射到不同大小的 NUMERIC 列。

Python 的类型系统提供了一种将附加元数据添加到 Python 类型的好方法,即使用 PEP 593 Annotated 通用类型,它允许将附加信息与 Python 类型捆绑在一起。 mapped_column() 结构将在 registry.type_annotation_map 中解析时通过标识正确地解释 Annotated 对象,如以下示例所示,我们声明了 StringNumeric 的两个变体

from decimal import Decimal

from typing_extensions import Annotated

from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry

str_30 = Annotated[str, 30]
str_50 = Annotated[str, 50]
num_12_4 = Annotated[Decimal, 12]
num_6_2 = Annotated[Decimal, 6]


class Base(DeclarativeBase):
    registry = registry(
        type_annotation_map={
            str_30: String(30),
            str_50: String(50),
            num_12_4: Numeric(12, 4),
            num_6_2: Numeric(6, 2),
        }
    )

传递给 Annotated 容器的 Python 类型,在上述示例中为 strDecimal 类型,仅对类型工具有用;就 mapped_column() 结构而言,它只需要在 registry.type_annotation_map 字典中执行每个类型对象的查找,而实际上不会查看 Annotated 对象的内部,至少在这个特定上下文中是如此。类似地,传递给 Annotated 的参数(除了底层 Python 类型本身之外)也不重要,只是为了使 Annotated 结构有效,至少必须有一个参数存在。然后,我们可以在映射中直接使用这些增强类型,它们将与更具体的类型构造匹配,如以下示例所示

class SomeClass(Base):
    __tablename__ = "some_table"

    short_name: Mapped[str_30] = mapped_column(primary_key=True)
    long_name: Mapped[str_50]
    num_value: Mapped[num_12_4]
    short_num_value: Mapped[num_6_2]

上述映射的 CREATE TABLE 将演示我们配置的 VARCHARNUMERIC 的不同变体,如下所示

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( short_name VARCHAR(30) NOT NULL, long_name VARCHAR(50) NOT NULL, num_value NUMERIC(12, 4) NOT NULL, short_num_value NUMERIC(6, 2) NOT NULL, PRIMARY KEY (short_name) )

虽然将 Annotated 类型链接到不同 SQL 类型的方式为我们提供了高度的灵活性,但下一节将演示 Annotated 在 Declarative 中使用的另一种更为开放的方式。

将完整的列声明映射到 Python 类型

上一节演示了使用 PEP 593 Annotated 类型实例作为 registry.type_annotation_map 字典中的键。在这种形式下,mapped_column() 结构实际上不会查看 Annotated 对象本身,它只是用作字典键。但是,Declarative 也有能力直接从 Annotated 对象中提取一个完整的预先建立的 mapped_column() 结构。使用这种形式,我们不仅可以定义与 Python 类型相关联的不同 SQL 数据类型,而无需使用 registry.type_annotation_map 字典,我们还可以以可重用方式设置任意数量的参数,例如可空性、列默认值和约束。

一组 ORM 模型通常会具有一些对所有映射类通用的主键风格。也可能有一些常见的列配置,例如带有默认值的 timestamps 和其他预先设置大小和配置的字段。我们可以将这些配置组合成 mapped_column() 实例,然后直接将它们捆绑到 Annotated 实例中,这些实例随后将在任意数量的类声明中重复使用。Declarative 在以这种方式提供时会解包一个 Annotated 对象,跳过任何不适用于 SQLAlchemy 的其他指令,并且只搜索 SQLAlchemy ORM 结构。

以下示例说明了以这种方式使用的各种预配置字段类型,我们在其中定义 intpk 代表一个 Integer 主键列,timestamp 代表一个 DateTime 类型,它将使用 CURRENT_TIMESTAMP 作为 DDL 级别的列默认值,以及 required_name,它是一个长度为 30 的 String,且为 NOT NULL

import datetime

from typing_extensions import Annotated

from sqlalchemy import func
from sqlalchemy import String
from sqlalchemy.orm import mapped_column


intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]
required_name = Annotated[str, mapped_column(String(30), nullable=False)]

上面的 Annotated 对象可以直接在 Mapped 中使用,其中预配置的 mapped_column() 结构将被提取并复制到一个新的实例中,该实例将特定于每个属性

class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[intpk]
    name: Mapped[required_name]
    created_at: Mapped[timestamp]

CREATE TABLE 对于我们上面的映射看起来像

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, name VARCHAR(30) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id) )

当以这种方式使用 Annotated 类型时,类型的配置也可能在每个属性的基础上受到影响。对于上面示例中显式使用 mapped_column.nullable 的类型,我们可以将 Optional[] 泛型修饰符应用于我们的任何类型,以便该字段在 Python 级别上是可选的或不是可选的,这将独立于在数据库中发生的 NULL / NOT NULL 设置

from typing_extensions import Annotated

import datetime
from typing import Optional

from sqlalchemy.orm import DeclarativeBase

timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False),
]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    # ...

    # pep-484 type will be Optional, but column will be
    # NOT NULL
    created_at: Mapped[Optional[timestamp]]

mapped_column() 结构也与显式传递的 mapped_column() 结构协调,其参数将优先于 Annotated 结构的参数。在下面,我们将 ForeignKey 约束添加到我们的整数主键,并为 created_at 列使用备用服务器默认值

import datetime

from typing_extensions import Annotated

from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.schema import CreateTable

intpk = Annotated[int, mapped_column(primary_key=True)]
timestamp = Annotated[
    datetime.datetime,
    mapped_column(nullable=False, server_default=func.CURRENT_TIMESTAMP()),
]


class Base(DeclarativeBase):
    pass


class Parent(Base):
    __tablename__ = "parent"

    id: Mapped[intpk]


class SomeClass(Base):
    __tablename__ = "some_table"

    # add ForeignKey to mapped_column(Integer, primary_key=True)
    id: Mapped[intpk] = mapped_column(ForeignKey("parent.id"))

    # change server default from CURRENT_TIMESTAMP to UTC_TIMESTAMP
    created_at: Mapped[timestamp] = mapped_column(server_default=func.UTC_TIMESTAMP())

CREATE TABLE 语句说明了这些每个属性的设置,添加了 FOREIGN KEY 约束,并将 UTC_TIMESTAMP 替换为 CURRENT_TIMESTAMP

>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, created_at DATETIME DEFAULT UTC_TIMESTAMP() NOT NULL, PRIMARY KEY (id), FOREIGN KEY(id) REFERENCES parent (id) )

注意

上面描述的 mapped_column() 的功能,其中可以使用 PEP 593 Annotated 对象来指示一组完整的列参数,这些对象包含一个要复制到属性中的“模板” mapped_column() 对象,目前尚未在其他 ORM 结构(例如 relationship()composite())中实现。虽然理论上这种功能是可能的,但目前尝试使用 Annotated 来指示 relationship() 等的更多参数将在运行时引发 NotImplementedError 异常,但在未来版本中可能会实现。

在类型映射中使用 Python Enum 或 pep-586 Literal 类型

版本 2.0.0b4 中的新功能: - 添加了 Enum 支持

版本 2.0.1 中的新功能: - 添加了 Literal 支持

从 Python 内置 enum.Enum 以及 typing.Literal 类派生的用户定义 Python 类型在 ORM 声明式映射中使用时,会自动链接到 SQLAlchemy Enum 数据类型。以下示例在 Mapped[] 构造函数中使用自定义的 enum.Enum

import enum

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


class Base(DeclarativeBase):
    pass


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class SomeClass(Base):
    __tablename__ = "some_table"

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

在上面的示例中,映射属性 SomeClass.status 将链接到一个数据类型为 Enum(Status)Column。例如,我们可以在 PostgreSQL 数据库的 CREATE TABLE 输出中看到这一点

CREATE TYPE status AS ENUM ('PENDING', 'RECEIVED', 'COMPLETED')

CREATE TABLE some_table (
  id SERIAL NOT NULL,
  status status NOT NULL,
  PRIMARY KEY (id)
)

类似地,可以使用 typing.Literal,使用一个包含所有字符串的 typing.Literal

from typing import Literal

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


class Base(DeclarativeBase):
    pass


Status = Literal["pending", "received", "completed"]


class SomeClass(Base):
    __tablename__ = "some_table"

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

registry.type_annotation_map 中使用的条目将基本 enum.Enum Python 类型以及 typing.Literal 类型链接到 SQLAlchemy Enum SQL 类型,使用一种特殊形式,它指示 Enum 数据类型应该自动针对任意枚举类型进行配置。这种默认情况下是隐式配置的,将被显式地指示为

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum),
        typing.Literal: sqlalchemy.Enum(enum.Enum),
    }

Declarative 中的解析逻辑能够解析 enum.Enum 的子类以及 typing.Literal 的实例,以匹配 registry.type_annotation_map 字典中的 enum.Enumtyping.Literal 条目。然后,Enum SQL 类型知道如何生成具有适当设置的自身配置版本,包括默认字符串长度。如果传递了一个不只包含字符串值的 typing.Literal,则会引发一个信息丰富的错误。

原生枚举和命名

参数 Enum.native_enum 指的是 Enum 数据类型是否应该创建一个所谓的“原生”枚举,在 MySQL/MariaDB 上是 ENUM 数据类型,在 PostgreSQL 上是 CREATE TYPE 创建的新的 TYPE 对象,或者是一个“非原生”枚举,这意味着将使用 VARCHAR 来创建数据类型。对于除 MySQL/MariaDB 或 PostgreSQL 之外的其他后端,VARCHAR 在所有情况下都使用(第三方方言可能具有自己的行为)。

由于 PostgreSQL 的 CREATE TYPE 要求为要创建的类型提供显式名称,因此在使用隐式生成的 Enum 且未在映射中指定显式 Enum 数据类型时,存在特殊的回退逻辑。

  1. 如果 Enumenum.Enum 对象相关联,则 Enum.native_enum 参数默认为 True,枚举的名称将从 enum.Enum 数据类型的名称获取。PostgreSQL 后端将假设使用此名称进行 CREATE TYPE

  2. 如果 Enumtyping.Literal 对象相关联,则 Enum.native_enum 参数默认为 False;不会生成名称,并假设为 VARCHAR

要在使用 PostgreSQL CREATE TYPE 类型的情况下使用 typing.Literal,必须使用显式的 Enum,无论是在类型映射中

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum("pending", "received", "completed", name="status_enum"),
    }

或者在 mapped_column()

import enum
import typing

import sqlalchemy
from sqlalchemy.orm import DeclarativeBase

Status = Literal["pending", "received", "completed"]


class Base(DeclarativeBase):
    pass


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    status: Mapped[Status] = mapped_column(
        sqlalchemy.Enum("pending", "received", "completed", name="status_enum")
    )
更改默认枚举的配置

为了修改隐式生成的 Enum 数据类型的固定配置,请在 registry.type_annotation_map 中指定新的条目,指示其他参数。例如,要无条件地使用“非原生枚举”,可以将 Enum.native_enum 参数设置为所有类型的 False。

import enum
import typing
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    type_annotation_map = {
        enum.Enum: sqlalchemy.Enum(enum.Enum, native_enum=False),
        typing.Literal: sqlalchemy.Enum(enum.Enum, native_enum=False),
    }

在版本 2.0.1 中更改: 实现了在 Enum 数据类型中覆盖 Enum.native_enum 等参数的支持,方法是在建立 registry.type_annotation_map 时进行。以前,此功能不起作用。

要对特定 enum.Enum 子类型使用特定配置,例如在使用示例 Status 数据类型时将字符串长度设置为 50。

import enum
import sqlalchemy
from sqlalchemy.orm import DeclarativeBase


class Status(enum.Enum):
    PENDING = "pending"
    RECEIVED = "received"
    COMPLETED = "completed"


class Base(DeclarativeBase):
    type_annotation_map = {
        Status: sqlalchemy.Enum(Status, length=50, native_enum=False)
    }

默认情况下,自动生成的 Enum 不与 Base 使用的 MetaData 实例关联,因此,如果元数据定义了一个模式,它将不会自动与枚举关联。要自动将枚举与元数据或枚举所属的表中的模式关联,可以设置 Enum.inherit_schema

from enum import Enum
import sqlalchemy as sa
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    metadata = sa.MetaData(schema="my_schema")
    type_annotation_map = {Enum: sa.Enum(Enum, inherit_schema=True)}
将特定的 enum.Enumtyping.Literal 链接到其他数据类型

上面的示例展示了使用 Enum,它会自动将自身配置为 enum.Enumtyping.Literal 类型对象上的参数/属性。对于需要将特定类型的 enum.Enumtyping.Literal 链接到其他类型的用例,也可以将这些特定类型放在类型映射中。在下面的示例中,将包含非字符串类型的 Literal[] 的条目链接到 JSON 数据类型

from typing import Literal

from sqlalchemy import JSON
from sqlalchemy.orm import DeclarativeBase

my_literal = Literal[0, 1, True, False, "true", "false"]


class Base(DeclarativeBase):
    type_annotation_map = {my_literal: JSON}

在上面的配置中,my_literal 数据类型将解析为 JSON 实例。其他 Literal 变体将继续解析为 Enum 数据类型。

mapped_column() 中的数据类功能

构造函数 mapped_column() 与 SQLAlchemy 的“原生数据类”功能集成,在 声明式数据类映射 中进行了讨论。有关 mapped_column() 支持的其他指令的当前背景,请参阅该部分。

访问表和元数据

声明式映射类将始终包含一个名为 __table__ 的属性;当使用 __tablename__ 的上述配置完成时,声明式过程会通过 __table__ 属性使 Table 可用

# access the Table
user_table = User.__table__

上面的表最终与对应于 Mapper.local_table 属性的表相同,我们可以通过 运行时检查系统 来查看。

from sqlalchemy import inspect

user_table = inspect(User).local_table

与声明式 registry 以及基类关联的 MetaData 集合通常是运行 CREATE 等 DDL 操作以及与 Alembic 等迁移工具一起使用时所必需的。此对象可以通过 registry 以及声明式基类的 .metadata 属性获得。下面,对于一个小脚本,我们可能希望针对 SQLite 数据库发出所有表的 CREATE

engine = create_engine("sqlite://")

Base.metadata.create_all(engine)

声明式表配置

在使用声明式表配置以及 __tablename__ 声明式类属性时,应使用 __table_args__ 声明式类属性提供要传递给 Table 构造函数的附加参数。

此属性同时支持通常传递给 Table 构造函数的位置参数和关键字参数。属性可以用两种形式之一来指定。其中一种是作为字典

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"mysql_engine": "InnoDB"}

另一种是元组,其中每个参数都是位置参数(通常是约束)

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
    )

关键字参数可以通过将最后一个参数指定为字典来与上述形式一起指定

class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = (
        ForeignKeyConstraint(["id"], ["remote_table.id"]),
        UniqueConstraint("foo"),
        {"autoload": True},
    )

类也可以使用 declared_attr() 方法装饰器以动态方式指定 __table_args__ 声明式属性以及 __tablename__ 属性。有关背景信息,请参见 使用 Mixin 编写映射层次结构

使用声明式表显式指定架构名称

Table 文档中所述,在 指定架构名称 中指定单个 Table 的架构名称,请使用 Table.schema 参数。使用声明式表时,此选项将与其他选项一样传递给 __table_args__ 字典

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __tablename__ = "sometable"
    __table_args__ = {"schema": "some_schema"}

架构名称也可以使用 MetaData.schema 参数全局应用于所有 Table 对象,该参数在 使用 MetaData 指定默认架构名称 中进行了介绍。可以通过直接将 MetaData 对象分别构造并将其与 DeclarativeBase 子类关联,并将该对象分配给 metadata 属性

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

metadata_obj = MetaData(schema="some_schema")


class Base(DeclarativeBase):
    metadata = metadata_obj


class MyClass(Base):
    # will use "some_schema" by default
    __tablename__ = "sometable"

另请参阅

指定架构名称 - 在 使用 MetaData 描述数据库 文档中。

为声明式映射的列设置加载和持久化选项

mapped_column() 结构接受其他特定于 ORM 的参数,这些参数会影响生成的 Column 的映射方式,从而影响其加载和持久化时间行为。常用的选项包括

  • 延迟列加载 - mapped_column.deferred 布尔值使用 Column 默认情况下使用 延迟列加载。在下面的示例中,User.bio 列默认情况下不会加载,只有在访问时才会加载

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        name: Mapped[str]
        bio: Mapped[str] = mapped_column(Text, deferred=True)

    另请参阅

    使用列延迟限制加载哪些列 - 延迟列加载的完整说明

  • 活动历史记录 - mapped_column.active_history 确保在属性的值发生更改时,先前值已加载并作为 AttributeState.history 集合的一部分,以便在检查属性的历史记录时进行检查。这可能会导致额外的 SQL 语句

    class User(Base):
        __tablename__ = "user"
    
        id: Mapped[int] = mapped_column(primary_key=True)
        important_identifier: Mapped[str] = mapped_column(active_history=True)

有关支持参数的列表,请参见 mapped_column() 的文档字符串。

另请参阅

为命令式表列应用加载、持久化和映射选项 - 描述使用 column_property()deferred() 用于命令式表配置

显式命名声明式映射的列

到目前为止,所有示例都包含与 ORM 映射属性关联的 mapped_column() 结构,其中传递给 mapped_column() 的 Python 属性名称也是列的名称,正如我们在 CREATE TABLE 语句以及查询中看到的那样。可以通过将字符串位置参数 mapped_column.__name 作为第一个位置参数传递,来指示 SQL 中表达的列的名称。在下面的示例中,User 类映射到列本身的备用名称

class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column("user_id", primary_key=True)
    name: Mapped[str] = mapped_column("user_name")

在上面的 User.id 解析为名为 user_id 的列,而 User.name 解析为名为 user_name 的列。我们可以使用 Python 属性名称编写 select() 语句,并将看到生成的 SQL 名称

>>> from sqlalchemy import select
>>> print(select(User.id, User.name).where(User.name == "x"))
SELECT "user".user_id, "user".user_name FROM "user" WHERE "user".user_name = :user_name_1

另请参阅

映射表列的备用属性名称 - 适用于命令式表

将其他列追加到现有的声明式映射类

声明式表配置允许在生成 Table 元数据之后,将新的 Column 对象添加到现有映射中。

对于使用声明式基类声明的声明式类,基础元类 DeclarativeMeta 包含一个 __setattr__() 方法,该方法将拦截其他 mapped_column() 或核心 Column 对象,并将它们添加到 Table 中(使用 Table.append_column())以及到现有的 Mapper 中(使用 Mapper.add_property()

MyClass.some_new_column = mapped_column(String)

使用核心 Column

MyClass.some_new_column = Column(String)

所有参数都支持,包括备用名称,例如 MyClass.some_new_column = mapped_column("some_name", String)。但是,SQL 类型必须显式传递给 mapped_column()Column 对象,如上面的例子所示,其中传递了 String 类型。 Mapped 注解类型无法参与此操作。

在使用单表继承的特定情况下,也可以将其他 Column 对象添加到映射中,其中映射子类上存在其他列,而这些子类没有自己的 Table。在 单表继承 部分中说明了这一点。

另请参阅

在声明后将关系添加到映射类 - 类似于 relationship() 的示例

注意

将映射属性分配给已映射的类只有在使用“声明式基类”时才能正常工作,这意味着用户定义的 DeclarativeBase 子类或由 declarative_base()registry.generate_base() 返回的动态生成的类。这个“基类”包含一个 Python 元类,它实现了一个特殊的 __setattr__() 方法,用于拦截这些操作。

如果类是使用装饰器(如 registry.mapped())或命令式函数(如 registry.map_imperatively())进行映射的,则在运行时将类映射的属性分配给映射类将不起作用

声明式与命令式表(又称混合声明式)

声明式映射也可以使用预先存在的 Table 对象,或者其他 Table 或其他任意 FromClause 结构(如 JoinSubquery),这些结构是单独构建的。

这被称为“混合声明式”映射,因为该类使用声明式样式映射与映射器配置相关的所有内容,但是映射的 Table 对象是单独生成的,并直接传递给声明式过程。

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


# construct a Table directly.  The Base.metadata collection is
# usually a good choice for MetaData but any MetaData
# collection may be used.

user_table = Table(
    "user",
    Base.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String),
    Column("fullname", String),
    Column("nickname", String),
)


# construct the User class using this table.
class User(Base):
    __table__ = user_table

在上面,Table 对象是使用在 使用元数据描述数据库 中描述的方法构建的。然后,它可以直接应用于声明式映射的类。 __tablename____table_args__ 声明式类属性在此形式中未使用。上述配置通常作为内联定义更具可读性。

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

上述样式的自然效果是,__table__ 属性本身是在类定义块中定义的。因此,它可以在随后的属性中立即引用,例如以下示例说明了在多态映射器配置中引用 type 列。

class Person(Base):
    __table__ = Table(
        "person",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String(50)),
        Column("type", String(50)),
    )

    __mapper_args__ = {
        "polymorphic_on": __table__.c.type,
        "polymorhpic_identity": "person",
    }

当非 Table 结构(如 JoinSubquery 对象)要进行映射时,也会使用“命令式表”形式。以下是一个示例。

from sqlalchemy import func, select

subq = (
    select(
        func.count(orders.c.id).label("order_count"),
        func.max(orders.c.price).label("highest_order"),
        orders.c.customer_id,
    )
    .group_by(orders.c.customer_id)
    .subquery()
)

customer_select = (
    select(customers, subq)
    .join_from(customers, subq, customers.c.id == subq.c.customer_id)
    .subquery()
)


class Customer(Base):
    __table__ = customer_select

有关映射到非 Table 结构的背景信息,请参阅 将类映射到多个表将类映射到任意子查询 部分。

当类本身使用其他形式的属性声明(如 Python 数据类)时,“命令式表”形式特别有用。有关详细信息,请参阅 将 ORM 映射应用于现有数据类(传统数据类使用) 部分。

映射表列的备用属性名称

显式命名声明式映射列 部分中,说明了如何使用 mapped_column() 为生成的 Column 对象提供一个与映射它的属性名称不同的特定名称。

在使用命令式表配置时,我们已经拥有 Column 对象。要将这些对象映射到备用名称,我们可以将 Column 直接分配给所需的属性。

user_table = Table(
    "user",
    Base.metadata,
    Column("user_id", Integer, primary_key=True),
    Column("user_name", String),
)


class User(Base):
    __table__ = user_table

    id = user_table.c.user_id
    name = user_table.c.user_name

上面的 User 映射将通过 User.idUser.name 属性引用 "user_id""user_name" 列,这与 显式命名声明式映射列 中演示的一样。

上述映射的一个注意事项是,在使用 PEP 484 键入工具时,直接内联链接到 Column 不会键入正确。解决此问题的策略是在 column_property() 函数中应用 Column 对象;虽然 Mapper 已经为其内部使用自动生成了此属性对象,但通过在类声明中对其进行命名,键入工具将能够将属性与 Mapped 注解匹配。

from sqlalchemy.orm import column_property
from sqlalchemy.orm import Mapped


class User(Base):
    __table__ = user_table

    id: Mapped[int] = column_property(user_table.c.user_id)
    name: Mapped[str] = column_property(user_table.c.user_name)

另请参阅

显式命名声明式映射列 - 适用于声明式表

为命令式表列应用加载、持久性和映射选项

设置声明式映射列的加载和持久化选项一节中,我们回顾了如何使用mapped_column()构造函数进行声明式表配置时,设置加载和持久化选项。当使用命令式表配置时,我们已经有了现有的Column对象被映射。为了将这些Column对象与特定于ORM映射的其他参数一起映射,我们可以使用column_property()deferred()构造函数来关联列的附加参数。选项包括

  • 延迟列加载 - deferred()函数是调用column_property()并设置column_property.deferred参数为True的简写;此构造函数默认情况下使用延迟列加载来建立Column。在下面的示例中,User.bio列默认不会加载,而只会在访问时加载

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("name", String),
        Column("bio", Text),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        bio = deferred(user_table.c.bio)

另请参阅

使用列延迟限制加载哪些列 - 延迟列加载的完整说明

  • 活动历史记录 - column_property.active_history确保在属性值发生变化时,先前值已被加载并成为AttributeState.history集合的一部分,以便在检查属性的历史记录时可以使用。这可能会导致额外的SQL语句

    from sqlalchemy.orm import deferred
    
    user_table = Table(
        "user",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("important_identifier", String),
    )
    
    
    class User(Base):
        __table__ = user_table
    
        important_identifier = column_property(
            user_table.c.important_identifier, active_history=True
        )

另请参阅

The column_property()构造函数对于类被映射到替代FROM子句(如联接和选择)的情况也很重要。有关这些情况的更多背景信息,请参见

对于使用mapped_column()的声明式表配置,大多数选项可以直接使用;有关示例,请参见设置声明式映射列的加载和持久化选项一节。

使用反射表进行声明式映射

有几种模式可用于创建映射到一系列Table对象的映射类,这些对象是使用反射数据库对象中描述的反射过程从数据库中内省得到的。

将类映射到从数据库中反射的表的简单方法是使用声明式混合映射,将Table.autoload_with参数传递给Table的构造函数

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


class MyClass(Base):
    __table__ = Table(
        "mytable",
        Base.metadata,
        autoload_with=engine,
    )

上面模式的一种变体是使用MetaData.reflect()方法一次反射一整套Table对象,然后从MetaData中引用它们

from sqlalchemy import create_engine
from sqlalchemy import Table
from sqlalchemy.orm import DeclarativeBase

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")


class Base(DeclarativeBase):
    pass


Base.metadata.reflect(engine)


class MyClass(Base):
    __table__ = Base.metadata.tables["mytable"]

使用__table__方法的一个缺点是,映射类必须在表被反射之后才能声明,这要求在声明应用程序类时数据库连接源必须存在;通常,类是在应用程序模块被导入时声明的,但是数据库连接在应用程序开始运行代码之前是不可用的,这样它就可以使用配置信息并创建一个引擎。目前有两种方法可以解决这个问题,将在接下来的两节中介绍。

使用延迟反射

为了适应声明映射类的用例,其中表元数据反射可以随后发生,一个名为DeferredReflection的简单扩展可用,它改变了声明式映射过程,将其延迟到调用类级别的DeferredReflection.prepare()方法时,该方法将对目标数据库执行反射过程,并将结果与声明式表映射过程集成,即使用__tablename__属性的类

from sqlalchemy.ext.declarative import DeferredReflection
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


class Reflected(DeferredReflection):
    __abstract__ = True


class Foo(Reflected, Base):
    __tablename__ = "foo"
    bars = relationship("Bar")


class Bar(Reflected, Base):
    __tablename__ = "bar"

    foo_id = mapped_column(Integer, ForeignKey("foo.id"))

在上面,我们创建了一个混合类Reflected,它将作为声明式层次结构中类的基类,这些类在调用Reflected.prepare方法时将被映射。在这样做之前,上面的映射并不完整,因为需要一个Engine

engine = create_engine("postgresql+psycopg2://user:pass@hostname/my_existing_database")
Reflected.prepare(engine)

The Reflected class的目的是定义类应该被反射映射的范围。插件将在.prepare()调用的目标的子类树中进行搜索,并反射所有由已声明类命名的表;目标数据库中不是映射的一部分且未通过外键约束与目标表相关的表不会被反射。

使用Automap

使用Automap扩展是针对现有数据库(其中将使用表反射)进行映射的更自动化的解决方案。此扩展将从数据库模式生成完整的映射类,包括基于观察到的外键约束的类之间的关系。虽然它包括用于自定义的钩子,例如允许自定义类命名和关系命名方案的钩子,但自动映射面向快速零配置工作方式。如果应用程序希望拥有一个使用表反射的完全显式的模型,DeferredReflection类可能更适合其更不自动化的方式。

另请参阅

Automap

从反射表中自动生成列命名方案

当使用任何先前的反射技术时,我们可以选择更改映射列的命名方案。The Column对象包含一个参数Column.key,它是一个字符串名称,确定此ColumnTable.c集合中存在的名称,与列的SQL名称无关。此键也被Mapper用作Column被映射的属性名称,如果未通过其他方式提供,例如映射表列的替代属性名称中所示。

当使用表反射时,我们可以使用DDLEvents.column_reflect()事件拦截用于Column的参数,因为它们被接收并应用我们需要的任何更改,包括.key属性,但也包括数据类型等。

事件钩子最容易与正在使用的MetaData对象相关联,如下所示

from sqlalchemy import event
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


@event.listens_for(Base.metadata, "column_reflect")
def column_reflect(inspector, table, column_info):
    # set column.key = "attr_<lower_case_name>"
    column_info["key"] = "attr_%s" % column_info["name"].lower()

使用上面的事件,Column对象的反射将被我们的事件拦截,该事件添加了一个新的“.key”元素,例如在下面的映射中

class MyClass(Base):
    __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)

此方法也适用于DeferredReflection基类以及Automap扩展。具体而言,对于自动映射,请参见拦截列定义一节以了解背景信息。

映射到一组明确的主键列

为了成功映射表,Mapper 构造函数始终要求至少识别出一列作为该可选择的“主键”。 这样,当加载或持久化 ORM 对象时,它可以与适当的 身份映射 一起放置,并使用适当的 身份键.

在那些要映射的反射表不包含主键约束的情况下,以及在 针对任意可选择内容的映射 的一般情况下,主键列可能不存在,Mapper.primary_key 参数被提供,以便任何一组列都可以被配置为该表的“主键”,就 ORM 映射而言。

给定以下针对现有 Table 对象的命令式表映射的示例,其中表没有任何声明的主键(如反射场景中可能发生的那样),我们可以像以下示例中那样映射这样的表

from sqlalchemy import Column
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import DeclarativeBase


metadata = MetaData()
group_users = Table(
    "group_users",
    metadata,
    Column("user_id", String(40), nullable=False),
    Column("group_id", String(40), nullable=False),
    UniqueConstraint("user_id", "group_id"),
)


class Base(DeclarativeBase):
    pass


class GroupUsers(Base):
    __table__ = group_users
    __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}

上面,group_users 表是某种类型的关联表,带有字符串列 user_idgroup_id,但没有设置主键;相反,只存在一个 UniqueConstraint,它表明这两列代表唯一键。Mapper 不会自动检查唯一约束以查找主键;相反,我们利用 Mapper.primary_key 参数,传递 [group_users.c.user_id, group_users.c.group_id] 的集合,表明这两列应用于构造 GroupUsers 类实例的身份键。

映射表列的子集

有时表反射可能会提供一个包含许多对我们需求不重要的列的 Table,并且可以安全地忽略这些列。 对于这样一张包含许多不需要在应用程序中引用的列的表,Mapper.include_propertiesMapper.exclude_properties 参数可以指示要映射的列的子集,其中目标 Table 中的其他列不会被 ORM 以任何方式考虑。 例如

class User(Base):
    __table__ = user_table
    __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

在上面的示例中,User 类将映射到 user_table 表,只包含 user_iduser_name 列 - 其余列不会被引用。

同样

class Address(Base):
    __table__ = address_table
    __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

Address 类映射到 address_table 表,包括所有存在的列,除了 streetcitystatezip

如这两个示例所示,可以按字符串名称或通过直接引用 Column 对象来引用列。 直接引用对象可能对明确性以及在映射到可能具有重复名称的多表结构时解决歧义很有用

class User(Base):
    __table__ = user_table
    __mapper_args__ = {
        "include_properties": [user_table.c.user_id, user_table.c.user_name]
    }

当列未包含在映射中时,这些列不会在执行 select() 或旧式 Query 对象时发出的任何 SELECT 语句中引用,映射类上也不会存在任何代表该列的映射属性;分配具有该名称的属性除了正常的 Python 属性分配之外不会有任何效果。

但是,重要的是要注意,即使那些 Column 对象可能被排除在 ORM 映射之外,**架构级别的列默认值仍然有效**。

“架构级别的列默认值”是指在 Column INSERT/UPDATE 默认值 中描述的默认值,包括由 Column.defaultColumn.onupdateColumn.server_defaultColumn.server_onupdate 参数配置的默认值。 这些构造继续具有正常的效果,因为在 Column.defaultColumn.onupdate 的情况下,Column 对象仍然存在于底层的 Table 上,从而允许默认函数在 ORM 发出 INSERT 或 UPDATE 时生效,而在 Column.server_defaultColumn.server_onupdate 的情况下,关系数据库本身会作为服务器端行为发出这些默认值。