使用声明式的表配置


正如 Declarative Mapping 中介绍的那样,Declarative 样式包括同时生成映射的 Table 对象的能力,或者容纳 Table 或其他 FromClause 对象。


以下示例假定声明性基类为:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


下面的所有示例都说明了从上述内容继承的类 基地使用 Decorator 的声明式映射中引入的 Decorator 样式(无声明性基) 也完全支持以下所有示例,旧版 Declarative Base 的形式,包括 declarative_base() 中。


带有 mapped_column() 的声明式表


使用 Declare 时,在大多数情况下,要映射的类的主体包括一个属性 __tablename__,该属性表示 应与映射一起生成的。这 mapped_column() 结构,它具有 plain 列中不存在的其他特定于 ORM 的配置功能 类,然后在类主体中使用 class 来指示表中的列。这 下面的示例说明了此结构在 声明性映射:

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() 结构内联放置在类中 定义作为类级别属性。在类为 declarative 映射过程,则声明式映射进程将生成一个新的 Table 对象; 每个 mapped_column() 将用于生成 Column 对象,该对象将成为此 TableTable.columns 集合的一部分 对象。


在上面的例子中,Declarative 将构建一个 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 字段通常被省略,因为声明式进程将使用给定给构造的属性名称并将其分配为列的名称(在上面的示例中,这是指名称 idnamefullnamenickname)。分配备选 mapped_column.__name 也有效,其中生成的 Column 将在 SQL 和 DDL 语句中使用给定的名称,而 User 映射类将继续允许使用给定的属性名称访问属性,而与列本身的名称无关(更多内容请参阅显式命名声明性映射列)。


提示


mapped_column() 结构仅在 声明性类映射。构造 Table 时 对象以及使用 命令式表配置,则仍然需要 Column 构造才能指示数据库列的存在。


另请参阅


映射表列 - 包含有关影响 Mapper 如何解释传入 Column 对象的附加说明。


使用带注释的声明式表 (Type Annotated Forms for mapped_column())¶


mapped_column() 构造能够从与 Declarative 映射类中声明的属性关联的 PEP 484 类型注释派生其列配置信息。这些类型注释(如果使用)必须 存在于名为 Mapped 的特殊 SQLAlchemy 类型中,该类型是一种泛型类型,然后指示其中的特定 Python 类型。


下面说明了上一节中的映射,添加了 映射:

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


在上面,当 Declarative 处理每个 class 属性时,每个 mapped_column() 将从左侧相应的 Mapped 类型注释中派生额外的参数,如果 目前。 此外,Declarative 将生成一个空的 mapped_column() 指令,每当 遇到没有分配给属性的值的 Map 类型 Comments(这种形式受到 Python 数据类中使用的类似样式的启发);这个 mapped_column() 结构继续从 Mapped 注释存在。


mapped_column()Mapped 注解中派生数据类型和可空性


mapped_column() 源自 映射的注释包括:


  • datatype - 在 Mapped 中给出的 Python 类型,包含在键入中。可选构造(如果存在)与 TypeEngine 子类,例如 IntegerStringDateTimeUuid 等几种常见类型。


    数据类型是根据 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 类型。


  • nullability - mapped_column() 结构将首先通过存在 mapped_column.nullable 参数(作为 TrueFalse)将其 Column 指示为 NULLNOT NULL。此外,如果 mapped_column.primary_key 参数存在并设置为 True,这也意味着该列应为 NOT NULL。


    在缺乏这两个参数的情况下,存在 打字。 Mapped 类型注释中的 Optional[] 将用于确定可空性,其中 typing.Optional[] 表示 NULL,并且没有键入。可选 [] 表示 NOT NULL。如果没有 Mapped[] 注解,并且没有 mapped_column.nullablemapped_column.primary_key 参数,则使用 SQLAlchemy 通常的 Column of 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() 的可空性与 Comments 所暗示的 null 性也是完全有效的。例如,ORM 映射属性可以注释为允许 None 在 Python 代码中,该代码在首次创建对象时与对象一起使用 和 populated,但该值最终将写入数据库 列的 NOT NULL 的 SET 的 Umapped_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 属性 无论出于何种原因,都需要在 schema 级别为 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.type_annotation_map 参数,该参数在首次使用时可能与 DeclarativeBase 超类相关联。


例如,如果我们希望将 BIGINT 数据类型用于 int,timezone=True 的 TIMESTAMP 数据类型 datetime.datetime,然后仅在我们要使用的 Microsoft SQL Server 上 NVARCHAR 数据类型时,注册表和声明性基可以配置为:

import datetime

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


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 类型的可配置性还有两个级别 在此之外可用,在接下来的两节中将介绍。


类型映射中的 Union 类型


在 2.0.37 版本发生变更: 本节中描述的功能已得到修复和增强,可以始终如一地工作。在此更改之前,type_annotation_map 支持 union 类型,但该功能 在 union 语法之间以及 没有得到处理。在尝试使用本节中描述的功能之前,请确保 SQLAlchemy 是最新的。


SQLAlchemy 支持将 type_annotation_map 内的联合类型映射到 允许映射可支持多种 Python 类型的数据库类型,例如 JSONJSONB

from typing import Union
from sqlalchemy import JSON
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.schema import CreateTable

# new style Union using a pipe operator
json_list = list[int] | list[str]

# old style Union using Union explicitly
json_scalar = Union[float, str, bool]


class Base(DeclarativeBase):
    type_annotation_map = {
        json_list: postgresql.JSONB,
        json_scalar: JSON,
    }


class SomeClass(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    list_col: Mapped[list[str] | list[int]]

    # uses JSON
    scalar_col: Mapped[json_scalar]

    # uses JSON and is also nullable=True
    scalar_col_nullable: Mapped[json_scalar | None]

    # these forms all use JSON as well due to the json_scalar entry
    scalar_col_newstyle: Mapped[float | str | bool]
    scalar_col_oldstyle: Mapped[Union[float, str, bool]]
    scalar_col_mixedstyle: Mapped[Optional[float | str | bool]]


上面的示例将 list[int]list[str] 的联合映射到 Postgresql JSONB 数据类型,同时命名 float 的并集 str 中,bool 将与 JSON 数据类型匹配。在 Mapped 构造中声明的等效联合将与类型 map 中的相应条目匹配。


联合类型的匹配基于联合的内容,而不管各个类型的命名方式如何,此外,不包括 None 类型的使用。也就是说,json_scalar也将匹配 str | bool | float | 。它不会与作为此联合的子集或超集的联合匹配;也就是说,str | bool 不会匹配,str | bool 也不会匹配 |float | int 的联合的各个内容(不包括 None)必须完全匹配。


就从 type_annotation_mapMapped 的匹配而言,None 值从来都不重要,但作为 Column 的可空性指标却很重要。当 None 存在于联合中时,因为它被放置在 Mapped 构造中。当出现在 Mapped 中时,它表示 将是可空的,在没有更具体的指示符的情况下。 这个逻辑是有效的 与指示 Optional 类型的方式相同,如 mapped_column() 从 Mapped 注解中派生数据类型和可为 null 性


上述映射的 CREATE TABLE 语句将如下所示:

>>> print(CreateTable(SomeClass.__table__).compile(dialect=postgresql.dialect()))
CREATE TABLE some_table ( id SERIAL NOT NULL, list_col JSONB NOT NULL, scalar_col JSON, scalar_col_not_null JSON NOT NULL, PRIMARY KEY (id) )


虽然联合类型使用匹配任何等效子类型集的“松散”匹配方法,但 Python 类型还提供了一种创建“类型别名”的方法,这些别名被视为与包含相同组合的另一种类型不等同的不同类型。将这些类型与 type_annotation_map 集成 在下一节 Support for Type Alias Types(由 PEP 695 定义)和 NewType 中介绍。


支持类型别名类型(由 PEP 695 定义)和 NewType


Union 类型中,Python 类型还包括两种使用类型以更正式的方式创建组合类型的方法,即类型。NewType 以及 PEP 695 中引入的 type 关键字。这些类型的行为与普通类型别名不同(即将类型分配给变量名称),并且 SQLAlchemy 如何从类型 Map 解析这些类型时,这种差异得到了体现。


在 2.0.37 版本发生变更: 本节中描述的键入行为。NewType (新类型) 以及 PEP 695类型已被正式化和更正。现在,对于在某些 2.0 版本中有效的“松散匹配”模式,会发出弃用警告,但在 SQLAlchemy 2.1 中将删除这些警告。在尝试使用本节中描述的功能之前,请确保 SQLAlchemy 是最新的。


typing 模块允许使用 typing 创建 “new types”。NewType

from typing import NewType

nstr30 = NewType("nstr30", str)
nstr50 = NewType("nstr50", str)


此外,在 Python 3.12 中,引入了由 PEP 695 定义的新功能,它提供 type 关键字来完成类似的任务;用 type 生成的对象在许多方面都类似于 typing。NewType (新类型) 这在内部称为 typing。TypeAliasType 的别名类型:

type SmallInt = int
type BigInt = int
type JsonScalar = str | float | bool | None


对于 SQLAlchemy 在 Mapped 中用于 SQL 类型查找时如何处理这些类型对象,请务必注意 Python 不考虑两种等效类型。TypeAliasType (类型别名类型)键入。NewType 对象的值相等:

# two typing.NewType objects are not equal even if they are both str
>>> nstr50 == nstr30
False

# two TypeAliasType objects are not equal even if they are both int
>>> SmallInt == BigInt
False

# an equivalent union is not equal to JsonScalar
>>> JsonScalar == str | float | bool | None
False


这与普通联合的比较方式相反,并告知 SQLAlchemy type_annotation_map的正确行为。使用键入时。NewTypePEP 695类型的对象,则类型对象应在 type_annotation_map 中是显式的,以便从 Mapped 类型进行匹配,其中必须按顺序声明相同的对象 进行匹配(不包括 也映射None 上的联合)。这与 Type Map 内的 Union types 中描述的行为不同,其中直接引用的普通 Union 将与其他 Union 匹配 基于特定类型的合成,而不是对象标识 在 type_annotation_map


在下面的示例中,nstr30nstr50SmallIntBigIntJsonScalar 彼此没有重叠,可以在每个 Mapped 构造中单独命名,并且在 type_annotation_map 中也是显式的。这些类型中的任何一个也可以与 None 联合或声明为 Optional[] 而不影响查找,仅派生列可空性:

from typing import NewType

from sqlalchemy import SmallInteger, BigInteger, JSON, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.schema import CreateTable

nstr30 = NewType("nstr30", str)
nstr50 = NewType("nstr50", str)
type SmallInt = int
type BigInt = int
type JsonScalar = str | float | bool | None


class TABase(DeclarativeBase):
    type_annotation_map = {
        nstr30: String(30),
        nstr50: String(50),
        SmallInt: SmallInteger,
        BigInteger: BigInteger,
        JsonScalar: JSON,
    }


class SomeClass(TABase):
    __tablename__ = "some_table"

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

    short_str: Mapped[nstr30]
    long_str_nullable: Mapped[nstr50 | None]

    small_int: Mapped[SmallInt]
    big_int: Mapped[BigInteger]
    scalar_col: Mapped[JsonScalar]


上述映射的 CREATE TABLE 将说明我们配置的 integer 和 string 的不同变体,如下所示:

>>> print(CreateTable(SomeClass.__table__))
CREATE TABLE some_table ( id INTEGER NOT NULL, normal_str VARCHAR NOT NULL, short_str VARCHAR(30) NOT NULL, long_str_nullable VARCHAR(50), small_int SMALLINT NOT NULL, big_int BIGINT NOT NULL, scalar_col JSON, PRIMARY KEY (id) )


关于可为 null 性,JsonScalar 类型的 None 包含在其 definition,它指示可为 null 的列。 同样, long_str_nullable列将 None 的并集应用于 nstr50,该联合与 type_annotation_map 中的 nstr50 类型匹配,同时还将可为空性应用于映射的列。其他列都保持 NOT NULL,因为它们未指示为可选。


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


由于单个 Python 类型可能与 TypeEngine 相关联 使用 registry.type_annotation_map parameter 中,一个额外的 功能是将单个 Python 类型与不同的 Python 类型相关联的能力 基于其他类型限定符的 SQL 类型的变体。 一个典型的 这方面的示例是将 Python str 数据类型映射到 VARCHAR 不同长度的 SQL 类型。 另一个是绘制不同种类的 十进制。Decimal 到不同大小的 NUMERIC 列。


Python 的类型系统提供了一种向 Python 类型添加额外元数据的好方法,即使用 PEP 593Annotated 泛型类型,该类型 允许将其他信息与 Python 类型捆绑在一起。这 mapped_column() 结构将正确解释 Annotated object 在 registry.type_annotation_map,如下例所示,我们声明了 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 对象的内部,至少在这个特定的上下文中是这样。同样,在底层 Python 类型本身之外传递给 Annotated 的参数也不重要,只是必须至少存在一个参数才能使 Annotated 结构有效。然后,我们可以直接在 Map 中使用这些增强类型,它们将与更具体的类型结构匹配,如下例所示:

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 593Annotated 类型实例作为 registry.type_annotation_map 中的 key 字典。 在这种形式中, mapped_column() 结构实际上并不查看 Annotated 对象本身,而是仅用作字典键。但是,Declarative 还能够直接从 Annotated 对象中提取整个预先建立的 mapped_column() 结构。使用这种形式,我们不仅可以定义链接到 Python 类型的不同种类的 SQL 数据类型,而无需使用 registry.type_annotation_map 字典,还可以以可重用的方式设置任意数量的参数,例如可空性、列默认值和约束。


一组 ORM 模型通常具有所有 Map 类通用的某种主键样式。也可能有常见的列配置,例如带有默认值的时间戳以及预先建立的大小和配置的其他字段。我们可以将这些配置组合成 mapped_column() 实例,然后直接捆绑到 Annotated 的实例中,然后在任意数量的类声明中重用。Declarative 将以这种方式提供时解压缩 Annotated 对象,跳过不适用于 SQLAlchemy 的任何其他指令,仅搜索 SQLAlchemy ORM 构造。


下面的示例说明了以这种方式使用的各种预配置字段类型,其中我们定义了表示 Integer 主键列的 intpk,表示 DateTime 类型的 timestamp,它将使用 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)]


然后,可以直接在 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 constraint 添加到我们的 Integer 主键中,并使用备用服务器 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 约束并替换了 CURRENT_TIMESTAMP UTC_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 包含“模板”的带注释对象 mapped_column() 对象复制到 attribute 中,则 目前没有为其他 ORM 结构实现,例如 relationship()composite() 的 Relationship() 进行转换。虽然此功能在理论上是可能的,但目前尝试使用 Annotated 指示 relationship() 和类似参数的更多参数将在运行时引发 NotImplementedError 异常,但可能会在未来版本中实现。


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


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


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


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

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


以类似的方式,键入。Literal 可以改用,使用键入。由所有字符串组成的 Literals:

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 中。枚举 Python 类型以及类型。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 的子类。枚举以及 typing 实例。Literal 匹配 enum 中。枚举键入。Literal 条目 registry.type_annotation_map字典。枚举 然后,SQL 类型知道如何使用 适当的设置,包括默认字符串长度。 如果键入。字面 ,它不仅包含字符串值,还会传递一个信息性 错误。


打字。TypeAliasType 还可用于创建枚举,方法是将它们分配给类型。字符串的文字

from typing import Literal

type Status = Literal["on", "off", "unknown"]


因为这是打字。TypeAliasType 中,它表示一个唯一的类型对象,因此必须将其放在 type_annotation_map 中才能成功查找,键控为 Enum 类型,如下所示:

import enum
import sqlalchemy


class Base(DeclarativeBase):
    type_annotation_map = {Status: sqlalchemy.Enum(enum.Enum)}


由于 SQLAlchemy 支持映射不同的类型。TypeAliasType (类型别名类型) 其他结构上单独等效的对象, 这些必须存在于 type_annotation_map 中以避免歧义。


原生 Enum 和命名


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


由于 PostgreSQL 的 CREATE TYPE 要求要创建的类型有一个显式名称,因此在使用 隐式生成的 Enum 而不在 Map 中指定显式 Enum 数据类型时,存在特殊的回退逻辑:


  1. 如果 Enum 链接到 enum。Enum 对象,Enum.native_enum 参数默认为 true,枚举的名称将取自 enum 中。enum 数据类型。PostgreSQL 后端将采用 CREATE TYPE 替换为此名称。


  2. 如果 Enum 链接到 typeing.Literal 对象,Enum.native_enum 参数默认为 错误;不生成名称,并假定 VARCHAR


使用键入。Literal 替换为 PostgreSQL CREATE TYPE 类型,则必须在类型映射中使用显式 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 的配置


为了修改 Enum 数据类型的固定配置 中,在 registry.type_annotation_map,指示其他参数。 例如,要无条件地使用 “non native enumerations”,则 对于所有类型的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.native_enum enum 数据类型。 registry.type_annotation_map。以前,此功能不起作用。


对特定枚举使用特定配置。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)}

链接特定枚举。枚举键入。Literal 转其他数据类型


上面的示例使用了 Enum,它会自动将自身配置为 Enum 上的参数/属性。枚举键入。Literal 类型对象。适用于特定类型的 enum.枚举键入。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 的 “原生 DataClasses”功能,在 声明性数据类映射。有关 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 以及基类通常是运行 DDL作(如 CREATE)以及与 Alembic 等迁移工具一起使用所必需的。此对象可通过 .metadata 获取 属性以及声明性基类。下面,对于一个小脚本,我们可能希望针对 SQLite 数据库的所有表发出 CREATE:

engine = create_engine("sqlite://")

Base.metadata.create_all(engine)


声明式表配置


当将声明式表配置与 __tablename__ declarative class 属性,则要提供给 Table 构造函数应使用 __table_args__声明性类属性。


此属性同时容纳位置和关键字 通常发送到 Table 构造函数。该属性可以采用两种形式之一指定。一种是字典:

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


另一个是 Tuples,其中每个参数都是位置性的(通常是 constraints):

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


__tablename____table_args__还可以使用 declared_attr() 方法装饰器。 看 使用 Mixin 编写映射的层次结构作为背景。


带有声明式表的显式 Schema Name


Table 的架构名称,如 指定 Schema Name 将应用于单个 Table 使用 Table.schema 参数。使用 Declarative table 时,此选项将像任何其他选项一样传递给 __table_args__ 字典:

from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
    pass


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


还可以使用使用 MetaData 指定默认架构名称中记录的 MetaData.schema 参数,将架构名称全局应用于所有 Table 对象。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"


为声明式映射列设置 load 和 persistence 选项


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


  • 延迟列加载 - mapped_column.deferred boolean 使用 默认情况下延迟列加载。在下面的示例中,默认情况下不会加载 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)


    另请参阅


    使用 Column Deferral 限制哪些列加载 - 延迟列加载的完整描述


  • active history - 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() 的文档字符串。


显式命名声明式映射列


到目前为止,所有示例都具有链接到 ORM 映射属性的 mapped_column() 结构,其中给定给 mapped_column() 的 Python 属性名称也是列的名称,如下所示 CREATE TABLE 语句以及查询。 列的名称为 可以通过传递字符串位置参数来表示 mapped_column.__name 作为第一个位置参数。在下面的示例中,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


另请参阅


映射表列的替代属性名称 - 适用于命令表


将额外的列附加到现有的 Declarative 映射类


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


对于使用声明性基类声明的声明性类,底层元类 DeclarativeMeta 包括 __setattr__() 将拦截其他 mapped_column() 或 Core 的方法 Column 对象,并使用 Table.append_column() 将它们添加到 Table 中 以及使用 Mapper.add_property() 的现有 Mapper

MyClass.some_new_column = mapped_column(String)


使用 core Column

MyClass.some_new_column = Column(String)


支持所有参数,包括备用名称,例如 MyClass.some_new_column = mapped_column("some_name", String) 。但是,SQL 类型必须传递给 mapped_column()Column 对象,如上面传递 String 类型的示例所示。Mapped annotation (映射的注释) 类型无法参与作。


在使用单个 table 继承的特定情况下,也可以将其他 Column 对象添加到 Map 中,其中其他列存在于没有自己的 Table 的 Map 子类上。 这在 单个表继承


另请参阅


声明后向映射类添加关系 - relationship() 的类似示例


注意


仅当使用 “声明性基” 类(即 DeclarativeBase 的用户定义子类或 declarative_base() 返回的动态生成的类)时,将映射属性分配给已映射的类才能正常工作 或 registry.generate_base() 的 API 中。这个 “base” 类包括一个 Python 元类,它实现了一个特殊的 __setattr__() 方法来拦截这些作。


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


Declarative with Imperative Table (又名 Hybrid Declarative)


声明式映射也可以提供预先存在的 Table 对象,或者 Table 或其他任意 FromClause 构造(例如 JoinSubquery)进行单独构造。


这被称为“混合声明式”映射,因为类是使用声明式样式映射涉及映射器配置的所有内容,但是映射的 Table object 单独生成并传递给声明式进程 径直:

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 对象是使用 使用 MetaData 描述数据库中描述的方法构造的。然后,它可以直接应用于以声明方式映射的类。__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() 为生成的 对象与映射它所对应的属性名称分开。


当使用 Imperative Table 配置时,我们已经有 存在 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_id”“user_name” 列,其方式与 显式命名声明性映射列 User.idUser.name 相同。


上述映射的一个警告是,指向 使用 PEP 484 打字工具。 解决此问题的策略是将 column_property() 中的 Column 对象 功能;而 Mapper 已经生成了这个属性 对象供内部使用,方法是在类 声明中,键入工具将能够将 attribute 与 映射的注释:

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)


另请参阅


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


为命令式表列应用 load、Persistence 和 Mapping 选项


为声明式映射列设置加载和持久性选项部分回顾了在将 mapped_column() 构造与声明式表配置一起使用时如何设置加载和持久性选项。当使用 Imperative Table 配置时,我们已经有 Maped 的现有 Column 对象。为了将这些 Column 对象与其他 特定于 ORM 映射的参数,我们可以使用 column_property()deferred() 结构,以便将其他参数与列相关联。选项包括:


  • 延迟列加载 - deferred() 函数column_property是使用 column_property.deferred 参数设置为 True;此构造使用 默认情况下延迟列加载。在下面的示例中,默认情况下不会加载 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 Deferral 限制哪些列加载 - 延迟列加载的完整描述


  • active history (活动历史记录) - 该 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
        )


另请参阅


column_property() 构造对于类映射到替代 FROM 子句(如 joins 和 selects)的情况也很重要。有关这些案件的更多背景信息,请访问:


对于使用 mapped_column() 的声明式表配置, 大多数选项都可以直接获得;请参阅该部分 为 Declarative Mapped Columns 设置 Load 和 Persistence 选项


使用反射表声明式映射


有几种模式可用于针对一系列 Table 对象生成映射类,这些对象是 从数据库中内省,使用 Reflecting Database 对象


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

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


上述模式中针对许多 table 进行缩放的变体是使用 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 mixin 可用,它可以更改声明性 映射过程将延迟到特殊类级别 调用 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"))


在上面,我们创建了一个 mixin 类 Reflected,它将作为声明式层次结构中类的基础,当调用 Reflected.prepare 方法时,这些类应该被映射。上述映射在给定 Engine 之前完成:

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


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


使用 Automap


针对要使用表反射的现有数据库进行映射的更自动化的解决方案是使用 Automap 扩展。这 扩展将从数据库架构生成整个映射类,包括 基于观察到的外键约束的类之间的关系。而 它包括用于自定义的 Hook,例如允许自定义的 Hooks 类命名和关系命名方案,automap 面向 权宜之计零配置的工作方式。如果应用程序希望拥有 一个利用表反射的完全显式模型, DeferredReflection (延迟反射) class 可能更可取,因为它的自动化程度较低。


另请参阅


自动映射


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


使用上述任何反射技术时,我们都有选项 以更改列的映射方式。 这 Column 对象包含参数 Column.key 这是一个字符串名称,用于确定此将以什么名称出现在 Table.c 中 集合,与列的 SQL 名称无关。 此键也是 被 Mapper 用作属性名称,在该名称下 如果未通过其他方式提供,则将映射,例如映射表列的替代属性名称中所示的方式。


在处理表反射时,我们可以在 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 扩展。具体而言,有关自动映射的信息,请参阅 Intercepting Column Definitions 部分了解背景信息。


映射到一组显式的主键列


为了成功映射表,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 反射可能会提供一个包含许多列的 Table,这些列对我们的需求并不重要,可以安全地忽略。对于具有大量不需要在应用程序中引用的列的表,Mapper.include_properties 或者Mapper.exclude_properties参数可以指示要映射的列的子集,其中 ORM 不会以任何方式考虑目标 Table 中的其他列。例:

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() 时发出的任何 SELECT 语句中引用 或遗留的 Query 对象,表示列的 mapped 类上也不会有任何 mapped 属性;分配该名称的属性除了普通 Python 属性分配之外,不会产生任何影响。


但是,请务必注意,架构级别列默认为 WILL 仍然对包含它们的 Column 对象有效,即使它们可能被排除在 ORM 映射之外。


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