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

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

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


另请参见

应用于命令式表列的加载、持久化和映射选项 - 描述了使用column_property()deferred()与命令式表配置一起使用 ### 明确命名声明式映射列

到目前为止,所有的例子都以 ORM 映射属性链接到mapped_column()构造为特色,其中 Python 属性名称赋予了mapped_column(),正如我们在 CREATE TABLE 语句和查询中看到的那样。在 SQL 中表示列的名称可以通过将字符串位置参数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 

另请参见

映射表列的备用属性名称 - 适用于命令式表 ### 向现有声明式映射类追加额外的列

声明式表配置允许在已生成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()返回的动态生成类。这个“基类”包括一个实现特殊__setattr__()方法的 Python 元类,用于拦截这些操作。

将类映射属性运行时分配给映射类,如果使用装饰器(registry.mapped())或命令式函数(registry.map_imperatively())来映射类,则不会起作用。### 使用注释声明表(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 注释派生的两个特点是:

  • 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 类型将被忽略。
  • 可空性 - mapped_column() 构造将首先通过 mapped_column.nullable 参数的存在与否来指示其 ColumnNULL 还是 NOT 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()的可为 null 性与注释所暗示的可为 null 性不同是完全有效的。例如,在使用对象进行首次创建和填充的 Python 代码中,ORM 映射的属性可能被注释为允许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)
  • 类似地,需要在模式级别为某些原因需要为 NULL 的数据库列写入的非 None 属性,可以将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超类相关联。

作为一个示例,如果我们希望使用BIGINT数据类型代表int,在datetime.datetime上使用带有timezone=TrueTIMESTAMP数据类型,并且仅在 Microsoft SQL Server 上使用NVARCHAR数据类型时,Python str 被使用,那么注册表和 Declarative base 可以被配置为:

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 类型上。mapped_column() 构造将正确地解释 Annotated 对象的身份,当在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 对象的内部,至少在这种特定上下文中是如此。同样,传递给 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 对象本身,它只被用作字典键。然而,Declarative 还具有直接从 Annotated 对象中提取整个预先建立的 mapped_column() 构造的能力。使用这种形式,我们不仅可以定义不同种类的 SQL 数据类型与 Python 类型的链接,而且可以以可重用的方式设置任意数量的参数,例如可为空性、列默认值和约束。

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

下面的示例演示了以这种方式使用的各种预配置字段类型,我们在其中定义了intpk表示一个Integer主键列,timestamp表示一个DateTime类型,它将使用CURRENT_TIMESTAMP作为 DDL 级别列默认值,并且required_name是一个长度为 30 的StringNOT 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中包含一个“模板”mapped_column()对象的Annotated对象来指示一组完整构造的列参数,这些参数将被复制到属性中,目前还没有实现到其他 ORM 构造中,例如relationship()composite()。虽然理论上可以实现这个功能,但当前尝试使用Annotated来指示对relationship()和类似方法的更多参数将在运行时引发NotImplementedError异常,但可能在未来版本中实现。 #### 在类型映射中使用 Python Enum或 pep-586 Literal类型

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

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

当在 ORM 声明式映射中使用时,从 Python 内置的enum.Enum以及typing.Literal类派生的用户定义的 Python 类型将自动链接到 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),
    }

在声明式中的解析逻辑能够解析 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 版本中更改:实现了在建立registry.type_annotation_map时覆盖参数(如Enum.native_enum)的支持。先前,此功能不起作用。

要为特定的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()中的数据类特性

mapped_column() 构造与 SQLAlchemy 的“原生数据类”功能集成,详见 声明性数据类映射。请参阅该部分了解 mapped_column() 支持的额外指令的当前背景。

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

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

  • 数据类型 - 在 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 类型将被忽略。
  • 可空性 - mapped_column() 构造将通过 mapped_column.nullable 参数的存在来首先指示其 ColumnNULLNOT 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)
  • 同样,一个非空属性写入到一个数据库列,由于某种原因需要在模式级别为 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)


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

相关文章
|
4月前
|
存储 SQL API
SqlAlchemy 2.0 中文文档(四)(5)
SqlAlchemy 2.0 中文文档(四)
32 3
|
4月前
|
SQL 测试技术 Python
SqlAlchemy 2.0 中文文档(四)(4)
SqlAlchemy 2.0 中文文档(四)
54 3
|
4月前
|
SQL 关系型数据库 测试技术
SqlAlchemy 2.0 中文文档(十)(1)
SqlAlchemy 2.0 中文文档(十)
30 1
|
4月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(2)
SqlAlchemy 2.0 中文文档(四)
43 1
|
4月前
|
SQL 数据库 Python
SqlAlchemy 2.0 中文文档(十)(3)
SqlAlchemy 2.0 中文文档(十)
35 1
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(三)(1)
SqlAlchemy 2.0 中文文档(三)
40 1
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(三)(2)
SqlAlchemy 2.0 中文文档(三)
32 1
|
4月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(十)(5)
SqlAlchemy 2.0 中文文档(十)
32 1
|
4月前
|
SQL 自然语言处理 数据库
SqlAlchemy 2.0 中文文档(二)(3)
SqlAlchemy 2.0 中文文档(二)
46 2
|
4月前
|
存储 Python
SqlAlchemy 2.0 中文文档(七)(5)
SqlAlchemy 2.0 中文文档(七)
26 1