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

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SqlAlchemy 2.0 中文文档(五)

使用声明性的映射类

原文:docs.sqlalchemy.org/en/20/orm/declarative_mapping.html

声明性映射风格是 SQLAlchemy 中主要使用的映射风格。请参阅 声明性映射 部分进行顶层介绍。

  • 声明性映射风格
  • 使用声明性基类
  • 使用装饰器进行声明性映射(无声明性基类)
  • 使用声明性的表配置
  • 具有mapped_column()的声明性表
  • 使用带注释的声明性表(mapped_column() 的类型注释形式)
  • 访问表和元数据
  • 声明性表配置
  • 使用声明性表的显式模式名称
  • 为声明性映射的列设置加载和持久化选项
  • 显式命名声明性映射列
  • 向现有的声明性映射类添加附加列
  • 声明性与命令式表(又名混合声明性)
  • 映射表列的备用属性名
  • 为命令式表列应用加载、持久化和映射选项
  • 使用反射表进行声明性映射
  • 使用延迟反射
  • 使用 Automap
  • 从反射表自动化列命名方案
  • 映射到明确一组主键列
  • 映射表列的子集
  • 使用声明性的映射器配置
  • 使用声明性定义映射属性
  • 使用声明性配置的 Mapper 配置选项
  • 动态构建映射器参数
  • 其他声明性映射指令
  • __declare_last__()
  • __declare_first__()
  • metadata
  • __abstract__
  • __table_cls__
  • 使用 Mixins 构建映射的层次结构
  • 增强基类
  • 混合列
  • 混合关联
  • 混合 _orm.column_property() 和其他 _orm.MapperProperty
  • 使用 Mixins 和基类与映射继承模式
  • 在继承 TableMapper 参数中使用 _orm.declared_attr()
  • 使用 _orm.declared_attr() 生成特定于表的继承列
  • 结合多个 Mixins 的表/映射器参数
  • 使用 Mixins 在 Mixins 上创建索引和约束

声明性映射样式

原文:docs.sqlalchemy.org/en/20/orm/declarative_styles.html

如在 声明性映射 中介绍的,声明性映射 是现代 SQLAlchemy 中构建映射的典型方式。本节将概述可用于声明性映射器配置的形式。

使用声明性基类

最常见的方法是通过将 DeclarativeBase 超类作为子类生成“声明性基类”:

from sqlalchemy.orm import DeclarativeBase
# declarative base class
class Base(DeclarativeBase):
    pass

也可以通过将现有的 registry 赋值为名为 registry 的类变量来创建声明性基类:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import registry
reg = registry()
# declarative base class
class Base(DeclarativeBase):
    registry = reg

在 2.0 版本中更改:DeclarativeBase 超类取代了 declarative_base() 函数和 registry.generate_base() 方法的使用;超类方法与 PEP 484 工具集成,无需使用插件。请参阅 ORM 声明模型 迁移说明。

使用声明性基类,新的映射类被声明为基类的子类:

from datetime import datetime
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name: Mapped[str]
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(64))
    create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    user: Mapped["User"] = relationship(back_populates="addresses")

上述中,Base 类作为新映射类的基础,如上所述,新映射类 UserAddress 被构建。

对于每个构建的子类,类的主体随后遵循声明性映射方法,该方法在幕后定义了既是 Table 也是 Mapper 对象的一体化映射。

另请参阅

使用声明性表配置 - 描述如何指定生成的映射 Table 的组件,包括关于使用 mapped_column() 构造的说明和选项,以及它与 Mapped 注解类型的交互方式。

使用声明性配置的映射器 - 描述了声明性中 ORM 映射器配置的所有其他方面,包括relationship()配置、SQL 表达式和Mapper参数 ## 使用装饰器的声明性映射(无声明性基类)

作为使用“声明性基类”的替代方案,可以将声明性映射明确应用于类,方法是使用类似于“经典”映射的命令式技术,或者更简洁地使用装饰器。 registry.mapped()函数是一个类装饰器,可应用于任何没有层次结构的 Python 类。否则,Python 类通常以声明性样式进行配置。

下面的示例设置了与上一节中看到的相同的映射,使用registry.mapped()装饰器而不是使用DeclarativeBase超类:

from datetime import datetime
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
mapper_registry = registry()
@mapper_registry.mapped
class User:
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name: Mapped[str]
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(64))
    create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
@mapper_registry.mapped
class Address:
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    user: Mapped["User"] = relationship(back_populates="addresses")

当使用上述风格时,特定类的映射在直接应用装饰器到该类时进行。对于继承映射(在映射类继承层次结构中详细描述),应将装饰器应用于要映射的每个子类:

from sqlalchemy.orm import registry
mapper_registry = registry()
@mapper_registry.mapped
class Person:
    __tablename__ = "person"
    person_id = mapped_column(Integer, primary_key=True)
    type = mapped_column(String, nullable=False)
    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "person",
    }
@mapper_registry.mapped
class Employee(Person):
    __tablename__ = "employee"
    person_id = mapped_column(ForeignKey("person.person_id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "employee",
    }

无论是使用声明性基类还是装饰器样式的声明性映射,都可以使用声明性表和命令式表表配置样式。

当将 SQLAlchemy 声明性映射与其他类仪器化系统(如dataclassesattrs)结合使用时,装饰器形式的映射很有用,尽管请注意,SQLAlchemy 2.0 现在也具有与声明性基类的 dataclasses 集成。 ## 使用声明性基类

最常见的方法是通过对DeclarativeBase超类进行子类化来生成“声明性基类”:

from sqlalchemy.orm import DeclarativeBase
# declarative base class
class Base(DeclarativeBase):
    pass

也可以通过将其分配为名为registry的类变量来使用现有的registry创建声明性基类:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import registry
reg = registry()
# declarative base class
class Base(DeclarativeBase):
    registry = reg

从版本 2.0 开始更改:DeclarativeBase超类取代了declarative_base()函数和registry.generate_base()方法的使用;超类方法集成了PEP 484工具,无需使用插件。有关迁移说明,请参见 ORM 声明模型。

使用声明性基类,新的映射类被声明为基类的子类:

from datetime import datetime
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
    pass
class User(Base):
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name: Mapped[str]
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(64))
    create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    user: Mapped["User"] = relationship(back_populates="addresses")

在上面,Base 类充当要映射的新类的基类,如上所述,构造了新的映射类UserAddress

对于每个构造的子类,类的主体随后遵循声明性映射方法,该方法在幕后定义了一个Table以及一个Mapper对象,它们组成了一个完整的映射。

另请参见

使用声明性进行表配置 - 描述了如何指定要生成的映射Table的组件,包括有关使用mapped_column()构造的注释和选项以及它与Mapped注解类型的交互方式。

使用声明性进行映射配置 - 描述了在声明中进行的 ORM 映射器配置的所有其他方面,包括relationship()配置、SQL 表达式和Mapper参数。

使用装饰器进行声明性映射(无声明基类)

作为使用“声明基类”类的替代方法是显式地将声明映射应用于类,可以使用类似于“传统”映射的命令式技术,也可以更简洁地使用装饰器。 registry.mapped() 函数是一个类装饰器,可以应用于任何没有层次结构的 Python 类。否则,Python 类通常以声明样式配置。

下面的示例设置了与前一部分中相同的映射,使用 registry.mapped() 装饰器而不是使用 DeclarativeBase 超类:

from datetime import datetime
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
mapper_registry = registry()
@mapper_registry.mapped
class User:
    __tablename__ = "user"
    id = mapped_column(Integer, primary_key=True)
    name: Mapped[str]
    fullname: Mapped[Optional[str]]
    nickname: Mapped[Optional[str]] = mapped_column(String(64))
    create_date: Mapped[datetime] = mapped_column(insert_default=func.now())
    addresses: Mapped[List["Address"]] = relationship(back_populates="user")
@mapper_registry.mapped
class Address:
    __tablename__ = "address"
    id = mapped_column(Integer, primary_key=True)
    user_id = mapped_column(ForeignKey("user.id"))
    email_address: Mapped[str]
    user: Mapped["User"] = relationship(back_populates="addresses")

在使用上述风格时,特定类的映射仅在直接将装饰器应用于该类时才会进行。对于继承映射(在映射类继承层次结构中详细描述),应该将装饰器应用于要映射的每个子类:

from sqlalchemy.orm import registry
mapper_registry = registry()
@mapper_registry.mapped
class Person:
    __tablename__ = "person"
    person_id = mapped_column(Integer, primary_key=True)
    type = mapped_column(String, nullable=False)
    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "person",
    }
@mapper_registry.mapped
class Employee(Person):
    __tablename__ = "employee"
    person_id = mapped_column(ForeignKey("person.person_id"), primary_key=True)
    __mapper_args__ = {
        "polymorphic_identity": "employee",
    }

无论是 声明式表 还是 命令式表 的表配置风格都可以与声明式映射的声明基类或装饰器风格一起使用。

使用装饰器形式的映射在将 SQLAlchemy 声明式映射与其他类的装配系统(如dataclassesattrs)结合时很有用,但要注意,SQLAlchemy 2.0 现在也支持在声明式基类中与 dataclasses 集成。

使用声明性进行表格配置

原文:docs.sqlalchemy.org/en/20/orm/declarative_tables.html

如 声明性映射 中所介绍的,声明性样式包括生成一个映射的 Table 对象的能力,或者直接适应一个 Table 或其他 FromClause 对象。

以下示例假定有一个声明性基类如下:

from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
    pass

接下来的所有示例都演示了从上述 Base 继承的类。在 使用装饰器的声明性映射(无声明性基类) 中介绍的装饰器样式以及通过 declarative_base() 生成的基类的遗留形式都得到了全面支持。

使用 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() 结构仅在 Declarative 类映射内有效。 在使用 Core 构造Table对象以及在使用 imperative table 配置时,仍然需要Column结构以指示数据库列的存在。

另请参阅

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

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

mapped_column() 结构能够从与 Declarative 映射类中声明的属性相关联的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))

在上述情况下,当 Declarative 处理每个类属性时,如果存在,每个mapped_column()将从左侧相应的Mapped类型注释派生出其他参数。 此外,当遇到没有分配给属性的Mapped类型注释时(这种形式受到了 Python dataclasses中使用的类似样式的启发)Declarative 将隐式生成一个空的mapped_column()指令;此mapped_column()结构继续从存在的Mapped注释派生其配置。

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

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

  • datatype - 在 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 类型将被忽略。
  • nullability - mapped_column() 构造将首先通过 mapped_column.nullable 参数的存在与设置为 TrueFalse 来指示其 ColumnNULLNOT NULL。此外,如果 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模块中。然而,协调声明映射过程的registry对象将首先查阅本地用户定义的类型字典,该字典可以在构造registry时作为参数registry.type_annotation_map传递,并且在首次使用时可以与DeclarativeBase超类相关联。

例如,如果我们希望使用BIGINT数据类型来表示int,带有timezone=TrueTIMESTAMP数据类型表示datetime.datetime,然后仅在 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 类型可能与任何类型的TypeEngine配置相关联,通过使用registry.type_annotation_map参数,额外的能力是能够将单个 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对象本身,而是仅用作字典键。然而,声明性还具有直接从Annotated对象中提取整个预先建立的mapped_column()构造的能力。使用这种形式,我们不仅可以定义与 Python 类型链接的不同种类的 SQL 数据类型,而无需使用registry.type_annotation_map字典,还可以以可重用的方式设置任意数量的参数,例如可为空性、列默认值和约束。

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

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

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

创建表语句说明了这些属性设置,添加了一个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()功能,其中可以使用包含“模板”mapped_column()对象的PEP 593 Annotated对象来指示完全构建的列参数集,目前尚未实现用于其他 ORM 构造,如relationship()composite()。虽然这种功能在理论上是可能的,但目前尝试使用Annotated来指示relationship()等的进一步参数将在运行时引发NotImplementedError异常,但可能会在未来的版本中实现。#### 在类型映射中使用 Python Enum 或 pep-586 Literal 类型

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

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

用户定义的 Python 类型,这些类型派生自 Python 内置的enum.Enum以及typing.Literal类,在 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将链接到一个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,使用由所有字符串组成的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 版本更改:在建立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()支持的其他指令的当前背景。 ### 访问表和元数据

声明性映射类将始终包括一个名为__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__属性。有关背景,请参阅使用混合组合映射层次结构。 ### 声明性表的显式架构名称

有关 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 中使用的 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"


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

相关文章
|
5月前
|
SQL 前端开发 数据库
SqlAlchemy 2.0 中文文档(六)(1)
SqlAlchemy 2.0 中文文档(六)
61 0
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(五)(5)
SqlAlchemy 2.0 中文文档(五)
64 4
|
5月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(四)(3)
SqlAlchemy 2.0 中文文档(四)
62 3
|
5月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(2)
SqlAlchemy 2.0 中文文档(四)
58 1
|
5月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(1)
SqlAlchemy 2.0 中文文档(四)
44 1
|
5月前
|
测试技术 API 数据库
SqlAlchemy 2.0 中文文档(十)(4)
SqlAlchemy 2.0 中文文档(十)
74 1
|
5月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(三)(1)
SqlAlchemy 2.0 中文文档(三)
51 1
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
123 2
|
5月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(4)
SqlAlchemy 2.0 中文文档(二)
52 2
|
5月前
|
SQL 测试技术 Python
SqlAlchemy 2.0 中文文档(二)(1)
SqlAlchemy 2.0 中文文档(二)
63 2