使用声明式的表配置¶
正如 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
对象,该对象将成为此 Table
的 Table.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
字段通常被省略,因为声明式进程将使用给定给构造的属性名称并将其分配为列的名称(在上面的示例中,这是指名称 id
、name
、fullname
、nickname
)。分配备选
mapped_column.__name
也有效,其中生成的
Column
将在 SQL 和 DDL 语句中使用给定的名称,而 User
映射类将继续允许使用给定的属性名称访问属性,而与列本身的名称无关(更多内容请参阅显式命名声明性映射列)。
提示
mapped_column()
结构仅在
声明性类映射。构造 Table
时
对象以及使用
命令式表配置,则仍然需要 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
子类,例如Integer
、String
和DateTime
或Uuid
等几种常见类型。
数据类型是根据 Python 类型的字典确定的 SQLAlchemy 数据类型。此字典是完全可自定义的,如下一节 自定义类型映射 中所述。默认类型映射的实现方式如下面的代码示例所示:from typing import Any from typing import Dict from typing import Type import datetime import decimal import uuid from sqlalchemy import types # default type mapping, deriving the type for mapped_column() # from a Mapped[] annotation type_map: Dict[Type[Any], TypeEngine[Any]] = { bool: types.Boolean(), bytes: types.LargeBinary(), datetime.date: types.Date(), datetime.datetime: types.DateTime(), datetime.time: types.Time(), datetime.timedelta: types.Interval(), decimal.Decimal: types.Numeric(), float: types.Float(), int: types.Integer(), str: types.String(), uuid.UUID: types.Uuid(), }
如果mapped_column()
构造指示传递给mapped_column.__type
参数的显式类型,则忽略给定的 Python 类型。
nullability -mapped_column()
结构将首先通过存在mapped_column.nullable
参数(作为True
或False
)将其Column
指示为NULL
或NOT NULL
。此外,如果mapped_column.primary_key
参数存在并设置为True
,这也意味着该列应为NOT NULL。
在缺乏这两个参数的情况下,存在打字。
Mapped
类型注释中的 Optional[] 将用于确定可空性,其中typing.Optional[]
表示NULL,
并且没有键入。可选 []
表示NOT NULL。
如果没有Mapped[]
注解,并且没有mapped_column.nullable
或mapped_column.primary_key
参数,则使用 SQLAlchemy 通常的Column
of NULL
默认值。
在下面的示例中,id
和data
列将为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 类型的数据库类型,例如
JSON
或 JSONB
:
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_map
到 Mapped
的匹配而言,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
的正确行为。使用键入时。NewType
或 PEP 695类型的
对象,则类型对象应在 type_annotation_map
中是显式的,以便从 Mapped
类型进行匹配,其中必须按顺序声明相同的对象
进行匹配(不包括
也映射
了 None
上的联合)。这与 Type Map 内的 Union types 中描述的行为不同,其中直接引用的普通 Union
将与其他 Union
匹配
基于特定类型的合成,而不是对象标识
在 type_annotation_map
。
在下面的示例中,nstr30
、nstr50
、
SmallInt
、BigInt
和 JsonScalar
彼此没有重叠,可以在每个 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
,如下例所示,我们声明了 String
和 Numeric
的两个变体:
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 类型,在上面的示例中,
str
和 Decimal
类型,仅对键入工具的好处很重要;就 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 将说明我们配置的 VARCHAR
和 NUMERIC
的不同变体,如下所示:
>>> 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
数据类型时,存在特殊的回退逻辑:
如果Enum
链接到enum。Enum
对象,Enum.native_enum
参数默认为true
,枚举的名称将取自enum 中。enum
数据类型。PostgreSQL 后端将采用CREATE TYPE
替换为此名称。
如果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()
的文档字符串。
另请参阅
为命令式表列应用加载、持久性和映射选项 - 描述使用
column_property()
和 deferred()
用于命令式表配置
显式命名声明式映射列¶
到目前为止,所有示例都具有链接到 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
构造(例如 Join
或 Subquery
)进行单独构造。
这被称为“混合声明式”映射,因为类是使用声明式样式映射涉及映射器配置的所有内容,但是映射的 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
构造(如 Join
或 Subquery
对象)将被映射。示例如下:
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.id
和 User.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_id
和 group_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_id
和 user_name
列 - 其余列不被引用。
同样地:
class Address(Base):
__table__ = address_table
__mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}
将 Address
类映射到 address_table
表,包括除 street
、city
、state
和 zip
之外的所有列。
如两个示例所示,可以通过字符串名称或直接引用 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.default
、Column.onupdate
、
Column.server_default
和
Column.server_onupdate
参数。这些构造
继续具有正常效果,因为在
Column.default
和 Column.onupdate
中,则
Column
对象仍然存在于底层
Table
的 API 中,因此允许默认函数在
ORM 发出 INSERT 或 UPDATE,在
Column.server_default
和
Column.server_onupdate
,关系数据库本身将这些默认值作为服务器端行为发出。