复合列类型¶
列集可以与单个用户定义的数据类型相关联,在现代使用中,该数据类型通常是 Python 数据类。ORM 提供了一个属性,该属性表示使用您提供的类的列组。
一个简单的示例将 Integer 列对表示为
Point 对象,具有属性 .x 和 .y。使用数据类,这些属性使用相应的 int 定义
Python 类型:
import dataclasses
@dataclasses.dataclass
class Point:
x: int
y: int
也接受非 dataclass 表单,但需要其他方法
待实施。 有关使用非 dataclass 类的示例,请参阅
使用旧版非数据类。
2.0 版本中的新功能: composite() 结构完全支持 Python 数据类,包括从 composite 类派生映射列数据类型的能力。
我们将创建一个到表顶点的映射,它将两个点表示为 x1/y1 和 x2/y2。Point 类使用 composite() 构造与映射的列相关联。
下面的示例说明了 composite() 的最现代形式为
与完全
带注释的声明式表
配置。表示每一列的 mapped_column() 结构直接传递给 composite(),表示零个或多个方面
要生成的列,在本例中为 names;这
composite() 结构派生列类型(在本例中为
int,对应于 Integer)直接从数据类中:
from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.orm import composite, mapped_column
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))
def __repr__(self):
return f"Vertex(start={self.start}, end={self.end})"
提示
在上面的示例中,表示复合体的列(x1、y1 等)也可以在类上访问,但类型检查器无法正确理解这些列。如果访问单个列很重要,则可以显式声明它们,如 直接映射列 中所示,将属性名称传递给 composite。
上面的映射将对应于 CREATE TABLE 语句,如下所示:
>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(Vertex.__table__))
CREATE TABLE vertices (
id INTEGER NOT NULL,
x1 INTEGER NOT NULL,
y1 INTEGER NOT NULL,
x2 INTEGER NOT NULL,
y2 INTEGER NOT NULL,
PRIMARY KEY (id)
)
使用映射的复合列类型¶
使用顶部所示的映射,我们可以使用
Vertex 类,其中 .start 和 .end 属性将透明地引用 Point 类引用的列,以及 Vertex 类的实例,其中 .start 和
.end 属性将引用 Point 类的实例。x1、
Y1、X2 和 Y2 列以透明方式处理:
持久化 Point 对象
我们可以创建一个Vertex对象,将Point对象分配为成员,它们将按预期持久化:>>> v = Vertex(start=Point(3, 4), end=Point(5, 6)) >>> session.add(v) >>> session.commit()
BEGIN (implicit) INSERT INTO vertices (x1, y1, x2, y2) VALUES (?, ?, ?, ?) [generated in ...] (3, 4, 5, 6) COMMIT
选择 Point 对象作为列composite()将允许Vertex.start和Vertex.end属性在一定程度上表现得像单个 SQL 表达式 当使用 ORMSession时(包括遗留的Query对象)以选择Point对象:>>> stmt = select(Vertex.start, Vertex.end) >>> session.execute(stmt).all()
SELECT vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices [...] ()[(Point(x=3, y=4), Point(x=5, y=6))]
比较 SQL 表达式中的 Point 对象Vertex.start和Vertex.end属性可用于 WHERE 条件和类似条件,使用临时Point对象进行比较:>>> stmt = select(Vertex).where(Vertex.start == Point(3, 4)).where(Vertex.end < Point(7, 8)) >>> session.scalars(stmt).all()
SELECT vertices.id, vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices WHERE vertices.x1 = ? AND vertices.y1 = ? AND vertices.x2 < ? AND vertices.y2 < ? [...] (3, 4, 7, 8)[Vertex(Point(x=3, y=4), Point(x=5, y=6))]
2.0 新版功能:composite()结构现在支持“排序”比较,例如<、>=等,此外还支持对==、!=的支持。
提示
上面使用 “小于” 运算符 (<) 的 “排序” 比较以及使用==的 “相等” 比较,当用于生成 SQL 表达式时,由Comparator实现 类,并且不要使用 composite 类上的 comparison 方法 本身,例如__lt__()或__eq__()方法。由此可见,上面的Point数据类也不需要实现数据类order=True参数,上述 SQL作即可工作。重新定义复合的比较作部分包含有关如何自定义比较作的背景。
更新 Vertex 实例上的 Point 对象
默认情况下,必须将Point对象替换为新对象才能检测到更改:>>> v1 = session.scalars(select(Vertex)).one()
SELECT vertices.id, vertices.x1, vertices.y1, vertices.x2, vertices.y2 FROM vertices [...] ()>>> v1.end = Point(x=10, y=14) >>> session.commit()UPDATE vertices SET x2=?, y2=? WHERE vertices.id = ? [...] (10, 14, 1) COMMIT
为了允许对复合对象进行就地更改, 必须使用 Mutation Tracking 扩展。 请参阅该部分 Establishing Mutability on Composites 为例。
复合材料的其他映射形式¶
可以使用 mapped_column() 构造、Column 或现有映射列的字符串名称将 composite() 构造传递给相关列。以下示例说明了与上述主要部分等效的映射。
直接映射列,然后传递给 composite¶
在这里,我们将现有的 mapped_column() 实例传递给
composite() 结构,如下面的未注释示例所示,我们还将 Point 类作为第一个参数传递给
composite() 的
from sqlalchemy import Integer
from sqlalchemy.orm import mapped_column, composite
class Vertex(Base):
__tablename__ = "vertices"
id = mapped_column(Integer, primary_key=True)
x1 = mapped_column(Integer)
y1 = mapped_column(Integer)
x2 = mapped_column(Integer)
y2 = mapped_column(Integer)
start = composite(Point, x1, y1)
end = composite(Point, x2, y2)
直接映射列,将 attribute name 传递给 composite¶
我们可以使用更多注释的形式编写上面相同的示例,其中我们可以选择将属性名称传递给 composite() 而不是全列结构:
from sqlalchemy.orm import mapped_column, composite, Mapped
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
x1: Mapped[int]
y1: Mapped[int]
x2: Mapped[int]
y2: Mapped[int]
start: Mapped[Point] = composite("x1", "y1")
end: Mapped[Point] = composite("x2", "y2")
命令式映射和命令式表¶
当使用命令式表或完全命令式映射时,我们可以直接访问 Column 对象。 这些可能会传递给
composite() 也是如此,如下面的命令式示例所示:
mapper_registry.map_imperatively(
Vertex,
vertices_table,
properties={
"start": composite(Point, vertices_table.c.x1, vertices_table.c.y1),
"end": composite(Point, vertices_table.c.x2, vertices_table.c.y2),
},
)
使用遗留的非数据类¶
如果不使用数据类,则自定义数据类型类的要求是它有一个构造函数,该构造函数接受与其列格式对应的位置参数,并且还提供一个方法 __composite_values__(),该方法将对象的状态作为列表或 Tuples 返回,按其基于列的属性的顺序。它还应该提供足够的 __eq__() 和 __ne__() 方法来测试两个实例的相等性。
为了说明主要部分中不使用数据类的等效 Point 类:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __composite_values__(self):
return self.x, self.y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __eq__(self, other):
return isinstance(other, Point) and other.x == self.x and other.y == self.y
def __ne__(self, other):
return not self.__eq__(other)
然后,在要与 Point 类关联的列也必须使用显式类型声明的情况下,继续使用复合的其他映射形式中的一种形式。
跟踪 Composites 的就地变更¶
不会自动跟踪对现有复合值的就地更改。相反,复合类需要显式地向其父对象提供事件。此任务在很大程度上通过使用 MutableComposite mixin 自动执行,该混合使用事件将每个用户定义的复合对象与所有父关联相关联。请参阅 Establishing Mutability on Composites 中的示例。
重新定义 Composites 的比较运算¶
默认情况下,“equals” 比较作会生成所有彼此相等的对应列的 AND。这可以使用 composite() 的 comparator_factory 参数来更改,其中我们指定一个自定义 Comparator 类来定义现有或新的作。下面我们说明了 “greater than” 运算符,它实现了与基 “greater than” 相同的表达式:
import dataclasses
from sqlalchemy.orm import composite
from sqlalchemy.orm import CompositeProperty
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.sql import and_
@dataclasses.dataclass
class Point:
x: int
y: int
class PointComparator(CompositeProperty.Comparator):
def __gt__(self, other):
"""redefine the 'greater than' operation"""
return and_(
*[
a > b
for a, b in zip(
self.__clause_element__().clauses,
dataclasses.astuple(other),
)
]
)
class Base(DeclarativeBase):
pass
class Vertex(Base):
__tablename__ = "vertices"
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[Point] = composite(
mapped_column("x1"), mapped_column("y1"), comparator_factory=PointComparator
)
end: Mapped[Point] = composite(
mapped_column("x2"), mapped_column("y2"), comparator_factory=PointComparator
)
由于 Point 是一个数据类,我们可以使用
dataclasses.astuple() 获取 Point 实例的元组形式。
然后,自定义比较器返回相应的 SQL 表达式:
>>> print(Vertex.start > Point(5, 6))
vertices.x1 > :x1_1 AND vertices.y1 > :y1_1
嵌套复合¶
可以将复合对象定义为在简单的嵌套方案中工作,方法是重新定义复合类中的行为以根据需要工作,然后将复合类正常映射到各个列的完整长度。这需要定义在 “nested” 和 “flat” 形式之间移动的其他方法。
下面我们将 Vertex 类重新组织为引用 Point 对象的复合对象。Vertex 和 Point 可以是数据类,但是我们将向 Vertex 添加一个自定义构造方法,该方法可用于在给定四个列值的情况下创建新的 Vertex 对象,该方法将任意命名 _generate() 并定义为类方法,以便我们可以通过将值传递给 Vertex._generate() 来创建新的 Vertex 对象
方法。
我们还将实现 __composite_values__() 方法,它是 composite() 结构(之前在使用旧版非数据类中介绍)识别的固定名称,它表示将对象作为列值的平面元组接收的标准方式,在这种情况下,它将取代通常的面向数据类的方法。
使用我们的自定义 _generate() 构造函数和
__composite_values__() 序列化器方法,我们现在可以在列的平面元组和包含 Point 的 Vertex 对象之间移动
实例。 Vertex._generate 方法作为第一个参数传递给 composite() 结构,作为 new 的源
Vertex 实例,__composite_values__() 方法将被 composite() 隐式使用。
对于该示例,Vertex 复合将映射到一个名为 HasVertex 的类,该类是包含四个源列的 Table 最终驻留的位置:
from __future__ import annotations
import dataclasses
from typing import Any
from typing import Tuple
from sqlalchemy.orm import composite
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
@dataclasses.dataclass
class Point:
x: int
y: int
@dataclasses.dataclass
class Vertex:
start: Point
end: Point
@classmethod
def _generate(cls, x1: int, y1: int, x2: int, y2: int) -> Vertex:
"""generate a Vertex from a row"""
return Vertex(Point(x1, y1), Point(x2, y2))
def __composite_values__(self) -> Tuple[Any, ...]:
"""generate a row from a Vertex"""
return dataclasses.astuple(self.start) + dataclasses.astuple(self.end)
class Base(DeclarativeBase):
pass
class HasVertex(Base):
__tablename__ = "has_vertex"
id: Mapped[int] = mapped_column(primary_key=True)
x1: Mapped[int]
y1: Mapped[int]
x2: Mapped[int]
y2: Mapped[int]
vertex: Mapped[Vertex] = composite(Vertex._generate, "x1", "y1", "x2", "y2")
然后,可以将上述映射用于 HasVertex、Vertex 和
要点:
hv = HasVertex(vertex=Vertex(Point(1, 2), Point(3, 4)))
session.add(hv)
session.commit()
stmt = select(HasVertex).where(HasVertex.vertex == Vertex(Point(1, 2), Point(3, 4)))
hv = session.scalars(stmt).first()
print(hv.vertex.start)
print(hv.vertex.end)
复合 API¶
对象名称 |
描述 |
|---|---|
|
|
-
函数 sqlalchemy.orm 中。composite(_class_or_attr:NoneType[_CC]Callable[...,_CC]_CompositeAttrType[Any]=None, *attrs: _CompositeAttrType[Any], group:strNone=None, deferred: bool = False, raiseload: bool = False, comparator_factory:Type[Composite.Comparator[_T]]None=None, active_history: bool = False, init:_NoArgbool=_NoArg.NO_ARG, repr:_NoArgbool=_NoArg.NO_ARG, default:AnyNone=_NoArg.NO_ARG, default_factory:_NoArgCallable[[],_T]=_NoArg.NO_ARG, compare:_NoArgbool=_NoArg.NO_ARG, kw_only:_NoArgbool=_NoArg.NO_ARG, hash:_NoArgboolNone=_NoArg.NO_ARG, info:_InfoTypeNone=None, doc:strNone=None, **__kw: Any) Composite[Any](复合[任意)]¶
返回用于 Mapper 的基于复合列的属性。
有关完整的用法示例,请参阅映射文档部分 Composite Column Types。composite()返回的MapperProperty是Composite。
参数
class_¶– “复合类型”类,或任何类方法或可调用对象,它将在给定列值的情况下按顺序生成复合对象的新实例。*attrs¶ –
要映射的元素列表,其中可能包括:Column对象
映射类上其他属性的字符串名称,可以是任何其他 SQL 或对象映射属性。例如,这可以允许引用多对一关系的复合
active_history=False¶– 当为 True时,表示标量属性的 “previous” 值应在替换时加载(如果尚未加载)。在column_property()上查看相同的标志。
group¶— 标记为 deferred时此属性的组名称。
deferred¶– 当 True 时,column 属性为 “deferred”,这意味着它确实如此 不会立即加载,而是在属性为 首次在实例上访问。 另请参阅deferred()的
comparator_factory¶ —— 一个扩展Comparator提供自定义 SQL 子句生成以进行比较作。
doc¶ – 可选字符串,将作为 doc 应用于类绑定描述符。
info¶– 可选的数据字典,将被填充到MapperProperty.info对象的属性。
default_factory¶ – 特定于 Declarative Dataclass Mapping,指定将作为__init__()的一部分发生的默认值生成函数 方法。
比较¶ –
特定于 Declarative Dataclass Mapping,指示此字段是否 在生成__eq__()和__ne__()方法。
2.0.0b4 版本的新Function。
kw_only¶ – 特定于 Declarative Dataclass Mapping,指示在生成__init__()时是否应将此字段标记为仅关键字。
哈希¶ –
特定于 Declarative Dataclass Mapping,控制在为映射的类生成__hash__()方法时是否包含此字段。
在 2.0.36 版本加入.