SqlAlchemy 2.0 中文文档(五)(3)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SqlAlchemy 2.0 中文文档(五)

SqlAlchemy 2.0 中文文档(五)(2)https://developer.aliyun.com/article/1563093


“模式级列默认值”指的是在列插入/更新默认值中描述的默认值,包括通过Column.defaultColumn.onupdateColumn.server_defaultColumn.server_onupdate参数配置的那些。这些构造仍然具有正常的效果,因为在Column.defaultColumn.onupdate的情况下,Column对象仍然存在于底层的Table上,因此当 ORM 发出 INSERT 或 UPDATE 时,允许默认函数发生作用,并且在Column.server_defaultColumn.server_onupdate的情况下,关系数据库本身会作为服务器端行为发出这些默认值。## 带有mapped_column()的声明性表格

在使用声明性时,要映射的类的主体在大多数情况下包括一个__tablename__属性,该属性指示应与映射一起生成的Table的字符串名称。然后,在类主体中使用了mapped_column()构造,该构造具有额外的 ORM 特定配置功能,这些功能不在普通的Column类中存在,以指示表中的列。下面的示例说明了在声明性映射中使用此构造的最基本用法:

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字段,该字段指示数据库列的名称,因为声明性过程将使用赋予构造的属性名称,并将其分配为列的名称(在上面的示例中,这指的是名称idnamefullnamenickname)。分配替代的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 类型注释时(此形式受到 Python dataclasses 中使用的相似风格的启发),声明式将隐式地生成一个空的 mapped_column() 指令;此 mapped_column() 构造随后从存在的 Mapped 注解中派生其配置。

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

mapped_column()Mapped 注释中派生的两个特性是:

  • 数据类型 - 在 Mapped 中给出的 Python 类型,如果存在 typing.Optional 构造,则与 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 类型将被忽略。
  • 可空性 - mapped_column() 构造将通过存在的 mapped_column.nullable 参数首先和主要指示其 Column 是否为 NULLNOT NULL,可以传递为 TrueFalse。此外,如果存在并且将 mapped_column.primary_key 参数设置为 True,那么这也将意味着该列应为 NOT NULL
    如果这两个参数都不存在,那么 Mapped 类型注释中的 typing.Optional[] 的存在将用于确定可空性,其中 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 模块中的硬编码字典。然而,协调 Declarative 映射过程的 registry 对象首先将会查看一个本地的、用户定义的类型字典,该字典可以在构造 registry 时作为 registry.type_annotation_map 参数传递,该参数可以与首次使用时与 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 类型

由于个别 Python 类型可以通过使用registry.type_annotation_map参数与任何类型的TypeEngine配置相关联,另一个功能是根据附加类型限定符将单个 Python 类型与 SQL 类型的不同变体关联起来的能力。其中一个典型示例是将 Python str数据类型映射到不同长度的VARCHAR SQL 类型。另一个示例是将不同种类的decimal.Decimal映射到不同大小的NUMERIC列。

Python 的类型系统提供了一种很好的方式来为 Python 类型添加附加元数据,即使用PEP 593 Annotated泛型类型,它允许将附加信息与 Python 类型捆绑在一起。当在registry.type_annotation_map中解析时,mapped_column()构造将正确地识别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 与声明式的方式,这种方式甚至更加开放。#### 将整个列声明映射到 Python 类型

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

一组 ORM 模型通常会有一种对所有映射类都通用的主键样式。还可能有常见的列配置,例如具有默认值的时间戳和其他预先设置大小和配置的字段。我们可以将这些配置组合成mapped_column()实例,然后直接捆绑到Annotated的实例中,然后在任意数量的类声明中重复使用。当以这种方式提供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 593Annotated 对象指示一组完整构造的列参数,该对象包含一个“模板” mapped_column() 对象,将被复制到属性中,目前尚未针对其他 ORM 构造(例如 relationship()composite())实现。虽然这种功能理论上是可能的,但目前尝试使用 Annotated 来指示对 relationship() 等的进一步参数将在运行时引发 NotImplementedError 异常,但可能会在未来的版本中实现。 #### 在类型映射中使用 Python Enum 或 pep-586 Literal 类型

新版本 2.0.0b4 中新增:- 添加了 Enum 支持

新版本 2.0.1 中新增:- 添加了 Literal 支持

当在 ORM 声明映射中使用时,用户定义的 Python 类型,其派生自 Python 内置的 enum.Enum 类以及 typing.Literal 类,将自动链接到 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 将链接到一个 Column,其数据类型为 Enum(Status)。我们可以在 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,其中包含了所有字符串:

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. 如果 Enum 链接到 enum.Enum 对象,则 Enum.native_enum 参数默认为 True,枚举的名称将从 enum.Enum 数据类型的名称中取。PostgreSQL 后端将假定使用此名称创建 CREATE TYPE
  2. 如果 Enum 链接到 typing.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.Enumtyping.Literal类型对象的Enum的使用。对于特定种类的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()中的 Dataclass 特性

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集合通常是运行 DDL 操作(例如 CREATE)以及与诸如 Alembic 之类的迁移工具一起使用的必要对象。此对象可通过registry以及声明性基类的.metadata属性获得。下面,对于一个小脚本,我们可能希望针对 SQLite 数据库发出所有表的 CREATE:

engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
```### 声明性表配置
当使用`__tablename__`声明性类属性进行声明性表配置时,应使用`__table_args__`声明性类属性提供额外的参数供`Table`构造函数使用。
此属性可容纳通常发送到`Table`构造函数的位置参数和关键字参数。该属性可以以两种形式之一指定。一种是作为字典:
```py
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子类关联:

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()的文档字符串以获取支持的参数列表。


SqlAlchemy 2.0 中文文档(五)(4)

相关文章
|
3月前
|
SQL 前端开发 数据库
SqlAlchemy 2.0 中文文档(六)(1)
SqlAlchemy 2.0 中文文档(六)
36 0
|
3月前
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(一)(1)
SqlAlchemy 2.0 中文文档(一)
104 1
SqlAlchemy 2.0 中文文档(一)(1)
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(五)(5)
SqlAlchemy 2.0 中文文档(五)
43 4
|
3月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(四)(3)
SqlAlchemy 2.0 中文文档(四)
36 3
|
3月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(一)(5)
SqlAlchemy 2.0 中文文档(一)
66 1
|
3月前
|
SQL 数据库 数据库管理
SqlAlchemy 2.0 中文文档(一)(2)
SqlAlchemy 2.0 中文文档(一)
80 1
|
3月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(2)
SqlAlchemy 2.0 中文文档(四)
30 1
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(4)
SqlAlchemy 2.0 中文文档(二)
31 2
|
3月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
40 2
|
3月前
|
SQL 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二)(3)
SqlAlchemy 2.0 中文文档(二)
29 2