原文:
docs.sqlalchemy.org/en/20/contents.html
排序列表
原文:
docs.sqlalchemy.org/en/20/orm/extensions/orderinglist.html
一个管理包含元素的索引/位置信息的自定义列表。
作者:
Jason Kirtland
orderinglist
是一个用于可变有序关系的辅助程序。它将拦截对由relationship()
管理的集合执行的列表操作,并自动将列表位置的更改同步到目标标量属性。
示例:一个slide
表,其中每行引用相关bullet
表中的零个或多个条目。幻灯片中的子弹根据bullet
表中position
列的值按顺序显示。当内存中重新排序条目时,position
属性的值应更新以反映新的排序顺序:
Base = declarative_base()
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position")
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
标准关系映射将在每个Slide
上产生一个类似列表的属性,其中包含所有相关的Bullet
对象,但无法自动处理顺序变化。将Bullet
附加到Slide.bullets
时,Bullet.position
属性将保持未设置状态,直到手动分配。当Bullet
插入列表中间时,后续的Bullet
对象也需要重新编号。
OrderingList
对象自动化此任务,管理集合中所有Bullet
对象的position
属性。它是使用ordering_list()
工厂构建的:
from sqlalchemy.ext.orderinglist import ordering_list
Base = declarative_base()
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
class Bullet(Base):
__tablename__ = 'bullet'
id = Column(Integer, primary_key=True)
slide_id = Column(Integer, ForeignKey('slide.id'))
position = Column(Integer)
text = Column(String)
使用上述映射,Bullet.position
属性被管理:
s = Slide()
s.bullets.append(Bullet())
s.bullets.append(Bullet())
s.bullets[1].position
>>> 1
s.bullets.insert(1, Bullet())
s.bullets[2].position
>>> 2
OrderingList
构造仅适用于对集合的更改,而不是从数据库的初始加载,并要求在加载时对列表进行排序。因此,请确保在针对目标排序属性的relationship()
上指定order_by
,以便在首次加载时排序正确。
警告
当主键列或唯一列是排序的目标时,OrderingList
在功能上提供的功能有限。不支持或存在问题的操作包括:
- 两个条目必须交换值。在主键或唯一约束的情况下,这不受直接支持,因为这意味着至少需要先暂时删除一行,或者在交换发生时将其更改为第三个中性值。
- 必须删除一个条目以为新条目腾出位置。SQLAlchemy 的工作单元在单次刷新中执行所有 INSERT 操作,然后再执行 DELETE 操作。在主键的情况下,它将交换相同主键的 INSERT/DELETE 以减轻此限制的影响,但对于唯一列不会发生这种情况。未来的功能将允许“DELETE before INSERT”行为成为可能,从而减轻此限制,但此功能将需要在映射器级别对要以这种方式处理的列集进行显式配置。
ordering_list()
接受相关对象的排序属性名称作为参数。默认情况下,对象在ordering_list()
中的位置与排序属性同步:索引 0 将获得位置 0,索引 1 位置 1,依此类推。要从 1 或其他整数开始编号,请提供count_from=1
。
API 参考
对象名称 | 描述 |
---|---|
count_from_0(index, collection) | 编号函数:从 0 开始的连续整数。 |
count_from_1(index, collection) | 编号函数:从 1 开始的连续整数。 |
count_from_n_factory(start) | 编号函数:从任意起始位置开始的连续整数。 |
ordering_list(attr[, count_from, ordering_func, reorder_on_append]) | 为映射器定义准备一个OrderingList工厂。 |
OrderingList | 一个自定义列表,用于管理其子项的位置信息。 |
function sqlalchemy.ext.orderinglist.ordering_list(attr: str, count_from: int | None = None, ordering_func: Callable[[int, Sequence[_T]], int] | None = None, reorder_on_append: bool = False) → Callable[[], OrderingList]
准备一个OrderingList
工厂,用于在映射器定义中使用。
返回一个适用于作为 Mapper 关系的collection_class
选项参数的对象。例如:
from sqlalchemy.ext.orderinglist import ordering_list
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
参数:
-
attr
– 用于存储和检索排序信息的映射属性的名称 -
count_from
– 设置从count_from
开始的基于整数的排序。例如,ordering_list('pos', count_from=1)
将在 SQL 中创建一个基于 1 的列表,将值存储在‘pos’列中。如果提供了ordering_func
,则会被忽略。
额外的参数传递给OrderingList
构造函数。
function sqlalchemy.ext.orderinglist.count_from_0(index, collection)
编号函数:从 0 开始的连续整数。
代码语言:javascript复制function sqlalchemy.ext.orderinglist.count_from_1(index, collection)
编号函数:从 1 开始的连续整数。
代码语言:javascript复制function sqlalchemy.ext.orderinglist.count_from_n_factory(start)
编号函数:从任意起始位置开始的连续整数。
代码语言:javascript复制class sqlalchemy.ext.orderinglist.OrderingList
一个自定义列表,用于管理其子项的位置信息。
OrderingList
对象通常使用 ordering_list()
工厂函数设置,与 relationship()
函数结合使用。
成员
init(), append(), insert(), pop(), remove(), reorder()
类签名
类 sqlalchemy.ext.orderinglist.OrderingList
(builtins.list
, typing.Generic
)
method __init__(ordering_attr: str | None = None, ordering_func: Callable[[int, Sequence[_T]], int] | None = None, reorder_on_append: bool = False)
一个自定义列表,用于管理其子项的位置信息。
OrderingList
是一个 collection_class
列表实现,它将 Python 列表中的位置与映射对象上的位置属性同步。
此实现依赖于列表以正确的顺序开始,因此一定要 确保 在关系上放置一个 order_by
。
参数:
-
ordering_attr
– 存储对象在关系中顺序的属性名称。 -
ordering_func
– 可选。将 Python 列表中的位置映射到存储在ordering_attr
中的值的函数。返回的值通常(但不必!)是整数。ordering_func
被调用时具有两个位置参数:列表中元素的索引和列表本身。 如果省略,将使用 Python 列表索引作为属性值。此模块提供了两个基本的预定义编号函数:count_from_0
和count_from_1
。有关诸如步进编号、字母编号和斐波那契编号等更奇特的示例,请参阅单元测试。 -
reorder_on_append
– 默认为 False。当追加一个具有现有(非 None)排序值的对象时,该值将保持不变,除非reorder_on_append
为真。这是一种优化,可以避免各种危险的意外数据库写入。 SQLAlchemy 将在对象加载时通过 append() 将实例添加到列表中。如果由于某种原因数据库的结果集跳过了排序步骤(例如,行 ‘1’ 缺失,但你得到了 ‘2’、‘3’ 和 ‘4’),那么 reorder_on_append=True 将立即重新编号为 ‘1’、‘2’、‘3’。如果有多个会话进行更改,其中任何一个会话恰巧加载了这个集合,即使是临时加载,所有会话都会尝试在它们的提交中“清理”编号,可能会导致除一个之外的所有会话都以并发修改错误失败。 建议保留默认值为 False,如果你在之前已经排序过的实例上进行append()
操作,或者在手动执行 SQL 操作后进行一些清理工作时,只需调用reorder()
即可。
method append(entity)
将对象追加到列表的末尾。
代码语言:javascript复制method insert(index, entity)
在索引之前插入对象。
代码语言:javascript复制method pop(index=-1)
移除并返回索引处的项目(默认为最后一个)。
如果列表为空或索引超出范围,则引发 IndexError。
代码语言:javascript复制method remove(entity)
移除第一次出现的值。
如果值不存在则引发 ValueError。
代码语言:javascript复制method reorder() → None
同步整个集合的排序。
扫描列表并确保每个对象设置了准确的排序信息。
API 参考
对象名称 | 描述 |
---|---|
count_from_0(index, collection) | 编号函数:从 0 开始的连续整数。 |
count_from_1(index, collection) | 编号函数:从 1 开始的连续整数。 |
count_from_n_factory(start) | 编号函数:从任意起始值开始的连续整数。 |
ordering_list(attr[, count_from, ordering_func, reorder_on_append]) | 为映射器定义准备一个OrderingList工厂。 |
OrderingList | 一个自定义列表,管理其子项的位置信息。 |
function sqlalchemy.ext.orderinglist.ordering_list(attr: str, count_from: int | None = None, ordering_func: Callable[[int, Sequence[_T]], int] | None = None, reorder_on_append: bool = False) → Callable[[], OrderingList]
为映射器定义准备一个OrderingList
工厂。
返回一个适合用作 Mapper 关系的collection_class
选项参数的对象。例如:
from sqlalchemy.ext.orderinglist import ordering_list
class Slide(Base):
__tablename__ = 'slide'
id = Column(Integer, primary_key=True)
name = Column(String)
bullets = relationship("Bullet", order_by="Bullet.position",
collection_class=ordering_list('position'))
参数:
-
attr
– 用于存储和检索排序信息的映射属性的名称 -
count_from
– 设置基于整数的排序,从count_from
开始。例如,ordering_list('pos', count_from=1)
将在 SQL 中创建一个以 1 为基础的列表,在‘pos’列中存储值。如果提供了ordering_func
,则忽略。
额外的参数将传递给OrderingList
构造函数。
function sqlalchemy.ext.orderinglist.count_from_0(index, collection)
编号函数:从 0 开始的连续整数。
代码语言:javascript复制function sqlalchemy.ext.orderinglist.count_from_1(index, collection)
编号函数:从 1 开始的连续整数。
代码语言:javascript复制function sqlalchemy.ext.orderinglist.count_from_n_factory(start)
编号函数:从任意起始值开始的连续整数。
代码语言:javascript复制class sqlalchemy.ext.orderinglist.OrderingList
一个自定义列表,管理其子项的位置信息。
OrderingList
对象通常使用与relationship()
函数配合使用的ordering_list()
工厂函数设置。
成员
init(), append(), insert(), pop(), remove(), reorder()
类签名
类 sqlalchemy.ext.orderinglist.OrderingList
(builtins.list
, typing.Generic
)
method __init__(ordering_attr: str | None = None, ordering_func: Callable[[int, Sequence[_T]], int] | None = None, reorder_on_append: bool = False)
一个自定义列表,用于管理其子项的位置信息。
OrderingList
是一个 collection_class
列表实现,将 Python 列表中的位置与映射对象上的位置属性同步。
此实现依赖于列表以正确顺序开始,因此请务必在关系上放置 order_by
。
参数:
-
ordering_attr
– 存储对象在关系中顺序的属性名称。 -
ordering_func
– 可选。将 Python 列表��的位置映射到存储在ordering_attr
中的值的函数。通常返回的值是整数(但不一定是!)。ordering_func
被调用时带有两个位置参数:列表中元素的索引和列表本身。 如果省略,则使用 Python 列表索引作为属性值。本模块提供了两个基本的预构建编号函数:count_from_0
和count_from_1
。有关更奇特的示例,如步进编号、字母和斐波那契编号,请参见单元测试。 -
reorder_on_append
– 默认为 False。在附加具有现有(非 None)排序值的对象时,该值将保持不变,除非reorder_on_append
为 true。这是一种优化,可避免各种危险的意外数据库写入。 当您的对象加载时,SQLAlchemy 将通过 append() 将实例添加到列表中。如果由于某种原因数据库的结果集跳过了排序步骤(例如,行‘1’丢失,但您得到‘2’、‘3’和‘4’),reorder_on_append=True 将立即重新编号项目为‘1’、‘2’、‘3’。如果有多个会话进行更改,其中任何一个碰巧加载此集合,即使是临时加载,所有会话都会尝试在其提交中“清理”编号,可能导致除一个之外的所有会话都因并发修改错误而失败。 建议保持默认值为 False,并在对先前有序实例进行append()
操作或在手动 sql 操作后进行一些清理时,只调用reorder()
。
method append(entity)
将对象追加到列表末尾。
代码语言:javascript复制method insert(index, entity)
在索引之前插入对象。
代码语言:javascript复制method pop(index=-1)
移除并返回索引处的项目(默认为最后一个)。
如果列表为空或索引超出范围,则引发 IndexError。
代码语言:javascript复制method remove(entity)
移除第一次出现的值。
如果值不存在,则引发 ValueError。
代码语言:javascript复制method reorder() → None
同步整个集合的排序。
扫描列表并确保每个对象具有准确的排序信息设置。
水平分片
原文:
docs.sqlalchemy.org/en/20/orm/extensions/horizontal_shard.html
水平分片支持。
定义了一个基本的“水平分片”系统,允许会话在多个数据库之间分发查询和持久化操作。
有关用法示例,请参见源分发中包含的水平分片示例。
深度炼金术
水平分片扩展是一个高级功能,涉及复杂的语句 -> 数据库交互以及对非平凡情况使用半公共 API。在使用这种更复杂且 less-production-tested 系统之前,应始终首先考虑更简单的引用多个数据库“分片”的方法,最常见的是每个“分片”使用一个独立的会话
。
API 文档
对象名称 | 描述 |
---|---|
set_shard_id | 一个加载器选项,用于为语句应用特定的分片 ID 到主查询,以及为其他关系和列加载器。 |
分片查询 | 与分片会话一起使用的查询类。 |
分片会话 |
class sqlalchemy.ext.horizontal_shard.ShardedSession
成员
init(), connection_callable(), get_bind()
类签名
类sqlalchemy.ext.horizontal_shard.ShardedSession
(sqlalchemy.orm.session.Session
)
method __init__(shard_chooser: ShardChooser, identity_chooser: Optional[IdentityChooser] = None, execute_chooser: Optional[Callable[[ORMExecuteState], Iterable[Any]]] = None, shards: Optional[Dict[str, Any]] = None, query_cls: Type[Query[_T]] = <class 'sqlalchemy.ext.horizontal_shard.ShardedQuery'>, *, id_chooser: Optional[Callable[[Query[_T], Iterable[_T]], Iterable[Any]]] = None, query_chooser: Optional[Callable[[Executable], Iterable[Any]]] = None, **kwargs: Any) → None
构造一个 ShardedSession。
参数:
-
shard_chooser
– 一个可调用对象,传入一个 Mapper、一个映射实例,可能还有一个 SQL 子句,返回一个分片 ID。该 ID 可能基于对象中存在的属性,或者基于某种轮询方案。如果方案基于选择,则应在实例上设置任何状态,以标记它在未来参与该分片。 -
identity_chooser
– 一个可调用对象,传入一个 Mapper 和主键参数,应返回一个主键可能存在的分片 ID 列表。 在 2.0 版本中更改:identity_chooser
参数取代了id_chooser
参数。 -
execute_chooser
– 对于给定的ORMExecuteState
,返回应发出查询的 shard_ids 列表。从所有返回的 shards 中返回的结果将合并到一个列表中。 在 1.4 版本中更改:execute_chooser
参数取代了query_chooser
参数。 -
shards
– 一个字符串分片名称到Engine
对象的字典。
method connection_callable(mapper: Mapper[_T] | None = None, instance: Any | None = None, shard_id: ShardIdentifier | None = None, **kw: Any) → Connection
提供一个用于工作单元刷新过程中使用的Connection
。
method get_bind(mapper: _EntityBindKey[_O] | None = None, *, shard_id: ShardIdentifier | None = None, instance: Any | None = None, clause: ClauseElement | None = None, **kw: Any) → _SessionBind
返回此Session
绑定的“bind”。
“bind”通常是Engine
的实例,除非Session
已经明确地直接绑定到Connection
的情况。
对于多重绑定或未绑定的Session
,使用mapper
或clause
参数确定要返回的适当绑定。
请注意,当通过 ORM 操作调用Session.get_bind()
时,通常会出现“映射器”参数,例如Session.query()
中的每个单独的 INSERT/UPDATE/DELETE 操作,在Session.flush()
调用中等。
解析顺序为:
- 如果提供了映射器并且
Session.binds
存在,则首先基于正在使用的映射器,然后基于正在使用的映射类,然后基于映射类的__mro__
中存在的任何基类,从更具体的超类到更一般的超类进行绑定定位。 - 如果提供了子句并且
Session.binds
存在,则基于在Session.binds
中找到的给定子句中存在的Table
对象进行绑定定位。 - 如果
Session.binds
存在,则返回该绑定。 - 如果提供了子句,则尝试返回一个与最终与该子句关联的
MetaData
绑定。 - 如果提供了映射器,尝试返回一个与最终与该映射器映射的
Table
或其他可选择对象关联的MetaData
绑定。 - 找不到绑定时,会引发
UnboundExecutionError
。
请注意,Session.get_bind()
方法可以在用户定义的 Session
子类上被重写,以提供任何类型的绑定解析方案。请参阅 自定义垂直分区 中的示例。
参数:
-
mapper
– 可选的映射类或相应的Mapper
实例。绑定可以首先从与此Session
关联的“binds”映射中查询Mapper
,其次从Mapper
映射到的Table
的MetaData
中查询绑定。 -
clause
– 一个ClauseElement
(即select()
、text()
等)。如果mapper
参数不存在或无法生成绑定,则将搜索给定的表达式构造,通常是与绑定的MetaData
关联的Table
。
另请参阅
分区策略(例如每个会话多个数据库后端)
Session.binds
Session.bind_mapper()
Session.bind_table()
class sqlalchemy.ext.horizontal_shard.set_shard_id
语句的加载器选项,用于将特定的分片 id 应用于主查询,以及额外的关系和列加载器。
set_shard_id
选项可以使用任何可执行语句的 Executable.options()
方法应用:
stmt = (
select(MyObject).
where(MyObject.name == 'some name').
options(set_shard_id("shard1"))
)
上面的语句在调用时将限制为主查询的“shard1”分片标识符,以及所有关系和列加载策略,包括像selectinload()
这样的急切加载器,像defer()
这样的延迟列加载器,以及惰性关系加载器lazyload()
。
这样,set_shard_id
选项的范围比在Session.execute.bind_arguments
字典中使用“shard_id”参数要广泛得多。
2.0.0 版本中的新功能。
成员
init(), propagate_to_loaders
类签名
类sqlalchemy.ext.horizontal_shard.set_shard_id
(sqlalchemy.orm.ORMOption
)
method __init__(shard_id: str, propagate_to_loaders: bool = True)
构造一个set_shard_id
选项。
参数:
-
shard_id
– 分片标识符 -
propagate_to_loaders
– 如果保持默认值True
,则分片选项将适用于诸如lazyload()
和defer()
之类的惰性加载器;如果为 False,则该选项不会传播到加载的对象。请注意,defer()
在任何情况下始终限制为父行的 shard_id,因此该参数仅对lazyload()
策略的行为产生净效果。
attribute propagate_to_loaders
如果为 True,则表示此选项应该在“次要”SELECT 语句中传递,这些语句发生在关系惰性加载器以及属性加载/刷新操作中。
代码语言:javascript复制class sqlalchemy.ext.horizontal_shard.ShardedQuery
与ShardedSession
一起使用的查询类。
遗留特性
ShardedQuery
是遗留Query
类的子类。 ShardedSession
现在通过ShardedSession.execute()
方法支持 2.0 风格的执行。
成员
set_shard()
类签名
类sqlalchemy.ext.horizontal_shard.ShardedQuery
(sqlalchemy.orm.Query
)的构造函数
method set_shard(shard_id: str) → Self
返回一个新的查询,限制为单个分片 ID。
返回的查询的所有后续操作将针对单个分片执行,而不考虑其他状态。
分片 ID 可以传递给 Session.execute()
的 bind_arguments 字典,以进行 2.0 样式的执行:
results = session.execute(
stmt,
bind_arguments={"shard_id": "my_shard"}
)
API 文档
对象名称 | 描述 |
---|---|
set_shard_id | 用于语句的加载器选项,以将特定的分片 ID 应用于主查询,以及额外的关系和列加载器。 |
ShardedQuery | 与 ShardedSession 一起使用的查询类。 |
ShardedSession |
class sqlalchemy.ext.horizontal_shard.ShardedSession
成员
init(), connection_callable(), get_bind()
类签名
类sqlalchemy.ext.horizontal_shard.ShardedSession
(sqlalchemy.orm.session.Session
)的构造函数
method __init__(shard_chooser: ShardChooser, identity_chooser: Optional[IdentityChooser] = None, execute_chooser: Optional[Callable[[ORMExecuteState], Iterable[Any]]] = None, shards: Optional[Dict[str, Any]] = None, query_cls: Type[Query[_T]] = <class 'sqlalchemy.ext.horizontal_shard.ShardedQuery'>, *, id_chooser: Optional[Callable[[Query[_T], Iterable[_T]], Iterable[Any]]] = None, query_chooser: Optional[Callable[[Executable], Iterable[Any]]] = None, **kwargs: Any) → None
构造一个 ShardedSession。
参数:
-
shard_chooser
– 一个可调用对象,传入 Mapper、映射实例和可能的 SQL 子句,返回一个分片 ID。此 ID 可能基于对象中存在的属性,或者基于某种循环选择方案。如果方案基于选择,则应在实例上设置将来标记其参与该分片的任何状态。 -
identity_chooser
– 一个可调用对象,传入 Mapper 和主键参数,应返回此主键可能存在的分片 ID 列表。 在 2.0 版本中更改:identity_chooser
参数取代了id_chooser
参数。 -
execute_chooser
– 对于给定的ORMExecuteState
,返回应发出查询的分片 ID 列表。返回的所有分片的结果将合并为单个列表。 在 1.4 版本中更改:execute_chooser
参数取代了query_chooser
参数。 -
shards
– 一个字符串分片名称到Engine
对象的字典。
method connection_callable(mapper: Mapper[_T] | None = None, instance: Any | None = None, shard_id: ShardIdentifier | None = None, **kw: Any) → Connection
提供一个用于工作单元刷新过程中使用的Connection
。
method get_bind(mapper: _EntityBindKey[_O] | None = None, *, shard_id: ShardIdentifier | None = None, instance: Any | None = None, clause: ClauseElement | None = None, **kw: Any) → _SessionBind
返回此Session
绑定的“绑定”。
“绑定”通常是Engine
的一个实例,除非Session
已经被显式地直接绑定到Connection
。
对于多重绑定或未绑定的Session
,将使用mapper
或clause
参数来确定要返回的适当绑定。
请注意,“mapper”参数通常在通过 ORM 操作调用Session.get_bind()
时存在,例如Session.query()
中的每个单独的 INSERT/UPDATE/DELETE 操作,在Session.flush()
调用等。
解析的顺序如下:
- 如果给定了映射器并且存在
Session.binds
,则首先基于正在使用的映射器,然后基于正在使用的映射类,然后基于映射类的__mro__
中存在的任何基类,从更具体的超类到更一般的超类。 - 如果给定了子句并且存在
Session.binds
,则基于在Session.binds
中找到的给定子句中的Table
对象定位绑定。 - 如果存在
Session.binds
,则返回该绑定。 - 如果给定了子句,则尝试返回与该子句最终相关联的
MetaData
相关的绑定。 - 如果给定了映射器,则尝试返回与最终与该映射器映射的
Table
或其他可选择对象相关联的MetaData
相关的绑定。 - 找不到绑定时,会引发
UnboundExecutionError
。
注意,Session.get_bind()
方法可以在Session
的用户定义子类上被覆盖,以提供任何类型的绑定解析方案。 请参阅自定义垂直分区中的示例。
参数:
-
mapper
– 可选的映射类或对应的Mapper
实例。 绑定可以首先通过查阅与此Session
关联的“binds”映射,其次通过查阅Mapper
映射到的Table
关联的MetaData
进行绑定。 -
clause
– 一个ClauseElement
(例如select()
,text()
等)。 如果未提供mapper
参数或无法生成绑定,则将搜索给定的表达式构造以查找绑定元素,通常是与绑定的MetaData
关联的Table
。
另请参阅
分区策略(例如每个 Session 的多个数据库后端)
Session.binds
Session.bind_mapper()
Session.bind_table()
class sqlalchemy.ext.horizontal_shard.set_shard_id
语句的加载器选项,可将特定的 shard id 应用于主查询以及用于额外的关系和列加载器。
可以使用任何可执行语句的Executable.options()
方法应用set_shard_id
选项:
stmt = (
select(MyObject).
where(MyObject.name == 'some name').
options(set_shard_id("shard1"))
)
在上述情况下,当调用语句时,主查询以及所有关系和列加载策略,包括诸如selectinload()
、延迟列加载器defer()
和惰性关系加载器lazyload()
等都将限制为“shard1”分片标识符。
这样,set_shard_id
选项的范围比在Session.execute.bind_arguments
字典中使用“shard_id”参数要广泛得多。
2.0.0 版本中的新功能。
成员
init(), propagate_to_loaders
类签名
类sqlalchemy.ext.horizontal_shard.set_shard_id
(sqlalchemy.orm.ORMOption
)
method __init__(shard_id: str, propagate_to_loaders: bool = True)
构造一个set_shard_id
选项。
参数:
-
shard_id
– 分片标识符 -
propagate_to_loaders
– 如果保持默认值True
,则shard
选项将对懒加载器(如lazyload()
和defer()
)生效;如果为False
,则该选项将不会传播到已加载的对象。请注意,defer()
无论如何都会限制到父行的shard_id
,因此该参数仅对lazyload()
策略的行为产生净效果。
attribute propagate_to_loaders
如果为True
,表示此选项应传递到关系懒加载器以及属性加载/刷新操作中发生的“次要”SELECT 语句。
class sqlalchemy.ext.horizontal_shard.ShardedQuery
与ShardedSession
一起使用的查询类。
遗留特性
ShardedQuery
是遗留Query
类的子类。ShardedSession
现在通过ShardedSession.execute()
方法支持 2.0 风格的执行。
成员
set_shard()
类签名
类 sqlalchemy.ext.horizontal_shard.ShardedQuery
(sqlalchemy.orm.Query
)
method set_shard(shard_id: str) → Self
返回一个新的查询,限定在单个分片 ID。
返回的查询的所有后续操作都将针对单个分片进行,而不考虑其他状态。
shard_id 可以传递给 Session.execute()
的 bind_arguments 字典,用于执行 2.0 风格的操作:
results = session.execute(
stmt,
bind_arguments={"shard_id": "my_shard"}
)
混合属性
原文:
docs.sqlalchemy.org/en/20/orm/extensions/hybrid.html
定义在 ORM 映射类上具有“混合”行为的属性。
“混合”意味着属性在类级别和实例级别具有不同的行为。
hybrid
扩展提供了一种特殊形式的方法装饰器,并且对 SQLAlchemy 的其余部分具有最小的依赖性。 它的基本操作理论可以与任何基于描述符的表达式系统一起使用。
考虑一个映射 Interval
,表示整数 start
和 end
值。 我们可以在映射类上定义更高级别的函数,这些函数在类级别生成 SQL 表达式,并在实例级别进行 Python 表达式评估。 下面,每个使用 hybrid_method
或 hybrid_property
装饰的函数可能会接收 self
作为类的实例,或者直接接收类,具体取决于上下文:
from __future__ import annotations
from sqlalchemy.ext.hybrid import hybrid_method
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class Interval(Base):
__tablename__ = 'interval'
id: Mapped[int] = mapped_column(primary_key=True)
start: Mapped[int]
end: Mapped[int]
def __init__(self, start: int, end: int):
self.start = start
self.end = end
@hybrid_property
def length(self) -> int:
return self.end - self.start
@hybrid_method
def contains(self, point: int) -> bool:
return (self.start <= point) & (point <= self.end)
@hybrid_method
def intersects(self, other: Interval) -> bool:
return self.contains(other.start) | self.contains(other.end)
上面,length
属性返回 end
和 start
属性之间的差异。 对于 Interval
的实例,这个减法在 Python 中发生,使用正常的 Python 描述符机制:
>>> i1 = Interval(5, 10)
>>> i1.length
5
处理 Interval
类本身时,hybrid_property
描述符将函数体评估为给定 Interval
类作为参数,当使用 SQLAlchemy 表达式机制评估时,将返回新的 SQL 表达式:
>>> from sqlalchemy import select
>>> print(select(Interval.length))
SELECT interval."end" - interval.start AS length
FROM interval
>>> print(select(Interval).filter(Interval.length > 10))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start > :param_1
过滤方法如 Select.filter_by()
也支持混合属性:
>>> print(select(Interval).filter_by(length=5))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval."end" - interval.start = :param_1
Interval
类示例还说明了两种方法,contains()
和 intersects()
,使用 hybrid_method
装饰。 这个装饰器将相同的思想应用于方法,就像 hybrid_property
将其应用于属性一样。 这些方法返回布尔值,并利用 Python 的 |
和 &
位运算符产生等效的实例级和 SQL 表达式级布尔行为:
>>> i1.contains(6)
True
>>> i1.contains(15)
False
>>> i1.intersects(Interval(7, 18))
True
>>> i1.intersects(Interval(25, 29))
False
>>> print(select(Interval).filter(Interval.contains(15)))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE interval.start <= :start_1 AND interval."end" > :end_1
>>> ia = aliased(Interval)
>>> print(select(Interval, ia).filter(Interval.intersects(ia)))
SELECT interval.id, interval.start,
interval."end", interval_1.id AS interval_1_id,
interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
FROM interval, interval AS interval_1
WHERE interval.start <= interval_1.start
AND interval."end" > interval_1.start
OR interval.start <= interval_1."end"
AND interval."end" > interval_1."end"
定义与属性行为不同的表达行为
在前一节中,我们在 Interval.contains
和 Interval.intersects
方法中使用 &
和 |
按位运算符是幸运的,考虑到我们的函数操作两个布尔值以返回一个新值。在许多情况下,Python 函数的构建和 SQLAlchemy SQL 表达式有足够的差异,因此应该定义两个独立的 Python 表达式。hybrid
装饰器为此目的定义了一个 修饰符 hybrid_property.expression()
。作为示例,我们将定义区间的半径,这需要使用绝对值函数:
from sqlalchemy import ColumnElement
from sqlalchemy import Float
from sqlalchemy import func
from sqlalchemy import type_coerce
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
在上述示例中,首先分配给名称 Interval.radius
的 hybrid_property
在后续使用 Interval._radius_expression
方法进行修改,使用装饰器 @radius.inplace.expression
,将两个修饰符 hybrid_property.inplace
和 hybrid_property.expression
连接在一起。使用 hybrid_property.inplace
指示 hybrid_property.expression()
修饰符应在原地突变现有的混合对象 Interval.radius
,而不是创建一个新对象。有关此修饰符及其基本原理的注释将在下一节 使用 inplace 创建符合 pep-484 的混合属性 中讨论。使用 @classmethod
是可选的,严格来说是为了给类型提示工具一个提示,即这种情况下 cls
应该是 Interval
类,而不是 Interval
的实例。
注意
hybrid_property.inplace
以及使用 @classmethod
进行正确类型支持的功能在 SQLAlchemy 2.0.4 中可用,之前的版本不支持。
Interval.radius
现在包含一个表达式元素,当在类级别访问 Interval.radius
时,会返回 SQL 函数 ABS()
:
>>> from sqlalchemy import select
>>> print(select(Interval).filter(Interval.radius > 5))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
```## 使用 `inplace` 创建符合 pep-484 的混合属性
在前一节中,说明了一个`hybrid_property`装饰器,其中包含两个独立的方法级函数被装饰,都用于生成一个称为`Interval.radius`的单个对象属性。实际上,我们可以使用几种不同的修饰符来修饰`hybrid_property`,包括`hybrid_property.expression()`、`hybrid_property.setter()`和`hybrid_property.update_expression()`。
SQLAlchemy 的`hybrid_property`装饰器意味着可以以与 Python 内置的`@property`装饰器相同的方式添加这些方法,其中惯用的用法是继续重定义属性,每次都使用**相同的属性名称**,就像下面的示例中演示的那样,说明了使用`hybrid_property.setter()`和`hybrid_property.expression()`来描述`Interval.radius`的用法:
```py
# correct use, however is not accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self):
return abs(self.length) / 2
@radius.setter
def radius(self, value):
self.length = value * 2
@radius.expression
def radius(cls):
return type_coerce(func.abs(cls.length) / 2, Float)
如上所述,有三个Interval.radius
方法,但由于每个都被hybrid_property
装饰器和@radius
名称本身装饰,因此最终效果是Interval.radius
是一个具有三个不同功能的单个属性。这种使用方式取自于Python 文档中对@property 的使用。值得注意的是,@property
以及hybrid_property
的工作方式,每次都会复制描述符。也就是说,每次调用@radius.expression
、@radius.setter
等都会完全创建一个新对象。这允许在子类中重新定义属性而无需问题(请参阅本节稍后的在子类中重用混合属性的使用方式)。
然而,上述方法与 mypy 和 pyright 等类型工具不兼容。 Python 自己的@property
装饰器之所以没有此限制,只是因为这些工具硬编码了@property 的行为,这意味着此语法不符合PEP 484的要求。
为了产生合理的语法,同时保持类型兼容性,hybrid_property.inplace
装饰器允许使用不同的方法名重复使用相同的装饰器,同时仍然在一个名称下生成一个装饰器:
# correct use which is also accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
# for example only
self.length = value * 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
使用hybrid_property.inplace
进一步限定了应该不制作新副本的装饰器的使用,从而保持了Interval.radius
名称,同时允许其他方法Interval._radius_setter
和Interval._radius_expression
命名不同。
2.0.4 版中的新功能:添加了hybrid_property.inplace
,以允许更少冗长的构造复合hybrid_property
对象,同时无需使用重复的方法名称。此外,允许在hybrid_property.expression
、hybrid_property.update_expression
和hybrid_property.comparator
内使用@classmethod
,以允许类型工具将cls
识别为类而不是方法签名中的实例。
定义设置器
hybrid_property.setter()
修饰符允许构造自定义的设置器方法,可以修改对象上的值:
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start value
现在,在设置时调用length(self, value)
方法:
>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17
允许批量 ORM 更新
一个混合可以为启用 ORM 的更新定义自定义的“UPDATE”处理程序,从而允许混合用于更新的 SET 子句中。
通常,在使用带有update()
的混合时,SQL 表达式被用作作为 SET 目标的列。如果我们的Interval
类有一个混合start_point
,它链接到Interval.start
,这可以直接替换:
from sqlalchemy import update
stmt = update(Interval).values({Interval.start_point: 10})
但是,当使用类似Interval.length
的复合混合类型时,此混合类型表示不止一个列。我们可以设置一个处理程序,该处理程序将适应传递给 VALUES 表达式的值,这可能会影响到这一点,使用hybrid_property.update_expression()
装饰器。一个类似于我们的设置器的处理程序将是:
from typing import List, Tuple, Any
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start value
@length.inplace.update_expression
def _length_update_expression(cls, value: Any) -> List[Tuple[Any, Any]]:
return [
(cls.end, cls.start value)
]
以上,如果我们在 UPDATE 表达式中使用Interval.length
,我们将得到一个混合 SET 表达式:
>>> from sqlalchemy import update
>>> print(update(Interval).values({Interval.length: 25}))
UPDATE interval SET "end"=(interval.start :start_1)
这个 SET 表达式会被 ORM 自动处理。
另请参阅
ORM-启用的 INSERT、UPDATE 和 DELETE 语句 - 包括 ORM 启用的 UPDATE 语句的背景信息
处理关系
创建与基于列的数据不同的混合对象时,本质上没有区别。对于不同的表达式的需求往往更大。我们将展示的两种变体是“连接依赖”混合和“相关子查询”混合。
连接依赖关系混合
考虑以下将 User
与 SavingsAccount
关联的声明性映射:
from __future__ import annotations
from decimal import Decimal
from typing import cast
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy import SQLColumnExpression
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Optional[Decimal]:
if self.accounts:
return self.accounts[0].balance
else:
return None
@balance.inplace.setter
def _balance_setter(self, value: Optional[Decimal]) -> None:
assert value is not None
if not self.accounts:
account = SavingsAccount(owner=self)
else:
account = self.accounts[0]
account.balance = value
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
return cast("SQLColumnExpression[Optional[Decimal]]", SavingsAccount.balance)
上面的混合属性 balance
与此用户的账户列表中的第一个 SavingsAccount
条目一起工作。在 Python 中的 getter/setter 方法可以将 accounts
视为可在 self
上使用的 Python 列表。
提示
在上面的例子中,User.balance
的 getter 方法访问了 self.accounts
集合,通常会通过配置在 User.balance
的 relationship()
上的 selectinload()
加载策略来加载。当在 relationship()
上没有另外指定时,默认的加载策略是 lazyload()
,它会按需发出 SQL。在使用 asyncio 时,像 lazyload()
这样的按需加载器不受支持,因此在使用 asyncio 时,应确保 self.accounts
集合对这个混合访问器是可访问的。
在表达式级别,预期 User
类将在适当的上下文中使用,以便存在与 SavingsAccount
的适当连接:
>>> from sqlalchemy import select
>>> print(select(User, User.balance).
... join(User.accounts).filter(User.balance > 5000))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
但需要注意的是,尽管实例级别的访问器需要担心 self.accounts
是否存在,但在 SQL 表达式级别,这个问题表现得不同,我们基本上会使用外连接:
>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print (select(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
相关子查询关系混合
当然,我们可以放弃依赖于包含查询中连接的使用,而选择相关子查询,它可以被打包成一个单列表达式。相关子查询更具可移植性,但在 SQL 层面通常性能较差。使用在 使用 column_property 中展示的相同技术,我们可以调整我们的 SavingsAccount
示例来聚合所有账户的余额,并使用相关子查询作为列表达式:
from __future__ import annotations
from decimal import Decimal
from typing import List
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Numeric
from sqlalchemy import select
from sqlalchemy import SQLColumnExpression
from sqlalchemy import String
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Decimal:
return sum((acc.balance for acc in self.accounts), start=Decimal("0"))
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
return (
select(func.sum(SavingsAccount.balance))
.where(SavingsAccount.user_id == cls.id)
.label("total_balance")
)
上面的示例将给我们一个 balance
列,它呈现一个相关的 SELECT:
>>> from sqlalchemy import select
>>> print(select(User).filter(User.balance > 400))
SELECT "user".id, "user".name
FROM "user"
WHERE (
SELECT sum(account.balance) AS sum_1 FROM account
WHERE account.user_id = "user".id
) > :param_1
构建自定义比较器
混合属性还包括一个辅助程序,允许构建自定义比较器。比较器对象允许单独定制每个 SQLAlchemy 表达式操作符的行为。在创建在 SQL 方面具有某些高度特殊行为的自定义类型时很有用。
注意
本节中引入的hybrid_property.comparator()
装饰器替换了hybrid_property.expression()
装饰器的使用。它们不能一起使用。
下面的示例类允许在名为word_insensitive
的属性上进行不区分大小写的比较:
from __future__ import annotations
from typing import Any
from sqlalchemy import ColumnElement
from sqlalchemy import func
from sqlalchemy.ext.hybrid import Comparator
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class CaseInsensitiveComparator(Comparator[str]):
def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
return func.lower(self.__clause_element__()) == func.lower(other)
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> str:
return self.word.lower()
@word_insensitive.inplace.comparator
@classmethod
def _word_insensitive_comparator(cls) -> CaseInsensitiveComparator:
return CaseInsensitiveComparator(cls.word)
在上述情况下,针对word_insensitive
的 SQL 表达式将对两侧应用LOWER()
SQL 函数:
>>> from sqlalchemy import select
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id, searchword.word
FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
上述的CaseInsensitiveComparator
实现了ColumnOperators
接口的部分内容。可以使用Operators.operate()
对所有比较操作(即eq
、lt
、gt
等)应用“强制转换”操作,如转换为小写:
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other, **kwargs):
return op(
func.lower(self.__clause_element__()),
func.lower(other),
**kwargs,
)
```## 在子类之间重用混合属性
可以从超类中引用混合体,以允许修改方法,如`hybrid_property.getter()`,`hybrid_property.setter()`,以便在子类中重新定义这些方法。这类似于标准 Python 的`@property`对象的工作原理:
```py
class FirstNameOnly(Base):
# ...
first_name: Mapped[str]
@hybrid_property
def name(self) -> str:
return self.first_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name = value
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
# 'inplace' is not used here; calling getter creates a copy
# of FirstNameOnly.name that is local to FirstNameLastName
@FirstNameOnly.name.getter
def name(self) -> str:
return self.first_name ' ' self.last_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name, self.last_name = value.split(' ', 1)
在上述情况下,FirstNameLastName
类引用了从FirstNameOnly.name
到子类的混合体,以重新利用其 getter 和 setter。
当仅在首次引用超类时覆盖hybrid_property.expression()
和hybrid_property.comparator()
作为类级别的第一个引用时,这些名称会与返回类级别的QueryableAttribute
对象上的同名访问器发生冲突。要在直接引用父类描述符时覆盖这些方法,请添加特殊限定词hybrid_property.overrides
,该限定词将仪表化的属性引用回混合对象:
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
@FirstNameOnly.name.overrides.expression
@classmethod
def name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
混合值对象
请注意,在我们之前的例子中,如果我们将SearchWord
实例的word_insensitive
属性与普通的 Python 字符串进行比较,普通的 Python 字符串不会被强制转换为小写 - 我们构建的CaseInsensitiveComparator
,由@word_insensitive.comparator
返回,仅适用于 SQL 端。
自定义比较器的更全面形式是构建一个混合值对象。这种技术将目标值或表达式应用于一个值对象,然后由访问器在所有情况下返回。值对象允许控制对值的所有操作,以及如何处理比较的值,无论是在 SQL 表达式方面还是在 Python 值方面。用新的CaseInsensitiveWord
类替换以前的CaseInsensitiveComparator
类:
class CaseInsensitiveWord(Comparator):
"Hybrid value representing a lower case representation of a word."
def __init__(self, word):
if isinstance(word, basestring):
self.word = word.lower()
elif isinstance(word, CaseInsensitiveWord):
self.word = word.word
else:
self.word = func.lower(word)
def operate(self, op, other, **kwargs):
if not isinstance(other, CaseInsensitiveWord):
other = CaseInsensitiveWord(other)
return op(self.word, other.word, **kwargs)
def __clause_element__(self):
return self.word
def __str__(self):
return self.word
key = 'word'
"Label to apply to Query tuple results"
在上面的例子中,CaseInsensitiveWord
对象表示self.word
,它可能是一个 SQL 函数,也可能是一个 Python 本机对象。通过重写operate()
和__clause_element__()
以使用self.word
,所有比较操作将针对“转换”形式的word
进行,无论是在 SQL 端还是在 Python 端。我们的SearchWord
类现在可以无条件地从单个混合调用中交付CaseInsensitiveWord
对象:
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> CaseInsensitiveWord:
return CaseInsensitiveWord(self.word)
word_insensitive
属性现在在所有情况下都具有不区分大小写的比较行为,包括 SQL 表达式与 Python 表达式(请注意,此处 Python 值在 Python 端转换为小写):
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL 表达式与 SQL 表达式:
代码语言:javascript复制>>> from sqlalchemy.orm import aliased
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print(
... select(sw1.word_insensitive, sw2.word_insensitive).filter(
... sw1.word_insensitive > sw2.word_insensitive
... )
... )
SELECT lower(searchword_1.word) AS lower_1,
lower(searchword_2.word) AS lower_2
FROM searchword AS searchword_1, searchword AS searchword_2
WHERE lower(searchword_1.word) > lower(searchword_2.word)
仅 Python 表达式:
代码语言:javascript复制>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print(ws1.word_insensitive)
someword
混合值模式非常适用于可能具有多种表示形式的任何类型的值,例如时间戳、时间差、测量单位、货币和加密密码。
另请参阅
混合和值不可知类型 - 在 techspot.zzzeek.org 博客上
值不可知类型,第二部分 - 在 techspot.zzzeek.org 博客上
API 参考
对象名称 | 描述 |
---|---|
比较器 | 一个帮助类,允许轻松构建用于混合使用的自定义PropComparator类。 |
hybrid_method | 一个装饰器,允许定义具有实例级和类级行为的 Python 对象方法。 |
hybrid_property | 一个装饰器,允许定义具有实例级和类级行为的 Python 描述符。 |
混合扩展类型 | 一个枚举类型。 |
class sqlalchemy.ext.hybrid.hybrid_method
一个装饰器,允许定义具有实例级和类级行为的 Python 对象方法。
成员
init(), expression(), extension_type, inplace, is_attribute
类签名
类sqlalchemy.ext.hybrid.hybrid_method
(sqlalchemy.orm.base.InspectionAttrInfo
, typing.Generic
)
method __init__(func: Callable[[Concatenate[Any, _P]], _R], expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]] | None = None)
创建一个新的hybrid_method
。
通常使用装饰器:
代码语言:javascript复制from sqlalchemy.ext.hybrid import hybrid_method
class SomeClass:
@hybrid_method
def value(self, x, y):
return self._value x y
@value.expression
@classmethod
def value(cls, x, y):
return func.some_function(cls._value, x, y)
代码语言:javascript复制method expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) → hybrid_method[_P, _R]
提供一个修改装饰器,定义一个生成 SQL 表达式的方法。
代码语言:javascript复制attribute extension_type: InspectionAttrExtensionType = 'HYBRID_METHOD'
扩展类型,如果有的话。默认为NotExtension.NOT_EXTENSION
另请参阅
HybridExtensionType
AssociationProxyExtensionType
attribute inplace
返回此hybrid_method
的原地变异器。
当调用hybrid_method.expression()
装饰器时,hybrid_method
类已经执行“原地”变异,因此此属性返回 Self。
版本 2.0.4 中的新功能。
另请参阅
使用 inplace 创建符合 pep-484 的混合属性
代码语言:javascript复制attribute is_attribute = True
如果这个对象是一个 Python 描述符,则为 True。
这可以指代许多类型之一。通常是一个处理属性事件的QueryableAttribute
,代表一个MapperProperty
。但也可以是一个扩展类型,如AssociationProxy
或hybrid_property
。InspectionAttr.extension_type
将指代一个标识特定子类型的常量。
另请参阅
Mapper.all_orm_descriptors
class sqlalchemy.ext.hybrid.hybrid_property
一个允许定义具有实例级和类级行为的 Python 描述符的装饰器。
成员
init(), comparator(), deleter(), expression(), extension_type, getter(), inplace, is_attribute, overrides, setter(), update_expression()
类签名
类sqlalchemy.ext.hybrid.hybrid_property
(sqlalchemy.orm.base.InspectionAttrInfo
, sqlalchemy.orm.base.ORMDescriptor
)
method __init__(fget: _HybridGetterType[_T], fset: _HybridSetterType[_T] | None = None, fdel: _HybridDeleterType[_T] | None = None, expr: _HybridExprCallableType[_T] | None = None, custom_comparator: Comparator[_T] | None = None, update_expr: _HybridUpdaterType[_T] | None = None)
创建一个新的hybrid_property
。
通常使用修饰器:
代码语言:javascript复制from sqlalchemy.ext.hybrid import hybrid_property
class SomeClass:
@hybrid_property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
代码语言:javascript复制method comparator(comparator: _HybridComparatorCallableType[_T]) → hybrid_property[_T]
提供一个修饰器,定义一个自定义比较器生成方法。
被修饰方法的返回值应该是Comparator
的一个实例。
注意
hybrid_property.comparator()
修饰器替代了hybrid_property.expression()
修饰器的使用。它们不能同时使用。
当在类级别调用混合属性时,这里给出的Comparator
对象被包装在一个专门的QueryableAttribute
中,这是 ORM 用来表示其他映射属性的对象。这样做的原因是为了在返回的结构中保留其他类级别属性,如文档字符串和对混合属性本身的引用,而不对传入的原始比较器对象进行任何修改。
注意
当引用拥有类(例如 SomeClass.some_hybrid
)的混合属性时,会返回一个QueryableAttribute
的实例,表示此混合对象的表达式或比较器对象。但是,该对象本身具有名为 expression
和 comparator
的访问器;因此,在尝试在子类上覆盖这些装饰器时,可能需要首先使用 hybrid_property.overrides
修饰符进行限定。有关详细信息,请参阅该修饰符。
method deleter(fdel: _HybridDeleterType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个删除方法。
代码语言:javascript复制method expression(expr: _HybridExprCallableType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个生成 SQL 表达式的方法。
当在类级别调用混合时,此处给出的 SQL 表达式将包装在一个专门的 QueryableAttribute
内,这是 ORM 用于表示其他映射属性的相同类型的对象。这样做的原因是为了在返回的结构中维护其他类级别属性,例如文档字符串和混合本身的引用,而不对传入的原始 SQL 表达式进行任何修改。
注意
当引用拥有类(例如 SomeClass.some_hybrid
)的混合属性时,会返回一个QueryableAttribute
的实例,表示表达式或比较器对象以及此混合对象。但是,该对象本身具有名为 expression
和 comparator
的访问器;因此,在尝试在子类上覆盖这些装饰器时,可能需要首先使用 hybrid_property.overrides
修饰符进行限定。有关详细信息,请参阅该修饰符。
请参见
定义与属性行为不同的表达行为
代码语言:javascript复制attribute extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'
扩展类型,如果有的话。默认为 NotExtension.NOT_EXTENSION
请参见
HybridExtensionType
AssociationProxyExtensionType
method getter(fget: _HybridGetterType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个获取器方法。
自 1.2 版开始新添加。
代码语言:javascript复制attribute inplace
返回此 hybrid_property
的原地变异器。
这是为了允许对混合进行就地变异,允许重用特定名称的第一个混合方法以添加更多方法,而不必将这些方法命名为相同的名称,例如:
代码语言:javascript复制class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
self.length = value * 2
@radius.inplace.expression
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
自 2.0.4 版开始新添加。
请参见
使用 inplace 创建符合 pep-484 的混合属性
代码语言:javascript复制attribute is_attribute = True
如果此对象是 Python 的 描述符,则为 True。
这可能是多种类型之一。通常是一个QueryableAttribute
,它代表一个MapperProperty
上的属性事件。但也可以是诸如AssociationProxy
或hybrid_property
之类的扩展类型。InspectionAttr.extension_type
将引用一个常量,以识别特定的子类型。
另请参阅
Mapper.all_orm_descriptors
attribute overrides
重写现有属性的方法的前缀。
hybrid_property.overrides
访问器只是返回这个混合对象,当在父类的类级别从父类调用时,它将取消引用通常在此级别返回的“instrumented attribute”,并允许修改装饰器,例如 hybrid_property.expression()
和 hybrid_property.comparator()
被使用而不与通常存在于QueryableAttribute
上的同名属性冲突:
class SuperClass:
# ...
@hybrid_property
def foobar(self):
return self._foobar
class SubClass(SuperClass):
# ...
@SuperClass.foobar.overrides.expression
def foobar(cls):
return func.subfoobar(self._foobar)
1.2 版的新功能。
另请参阅
在子类中重用混合属性
代码语言:javascript复制method setter(fset: _HybridSetterType[_T]) → hybrid_property[_T]
提供一个定义 setter 方法的修改装饰器。
代码语言:javascript复制method update_expression(meth: _HybridUpdaterType[_T]) → hybrid_property[_T]
提供一个定义产生 UPDATE 元组的修改装饰器方法。
该方法接受一个值,该值将被渲染到 UPDATE 语句的 SET 子句中。然后,该方法应将此值处理为适合最终 SET 子句的单个列表达式,并将它们作为 2 元组的序列返回。每个元组包含一个列表达式作为键和要渲染的值。
例如:
代码语言:javascript复制class Person(Base):
# ...
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def fullname(self):
return first_name " " last_name
@fullname.update_expression
def fullname(cls, value):
fname, lname = value.split(" ", 1)
return [
(cls.first_name, fname),
(cls.last_name, lname)
]
1.2 版的新功能。
代码语言:javascript复制class sqlalchemy.ext.hybrid.Comparator
一个辅助类,允许轻松构建自定义PropComparator
类以与混合一起使用。
类签名
类sqlalchemy.ext.hybrid.Comparator
(sqlalchemy.orm.PropComparator
)
class sqlalchemy.ext.hybrid.HybridExtensionType
一个枚举。
成员
HYBRID_METHOD, HYBRID_PROPERTY
类签名
类sqlalchemy.ext.hybrid.HybridExtensionType
(sqlalchemy.orm.base.InspectionAttrExtensionType
)
attribute HYBRID_METHOD = 'HYBRID_METHOD'
表示一个InspectionAttr
的符号,其类型为hybrid_method
。
被赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes
attribute HYBRID_PROPERTY = 'HYBRID_PROPERTY'
表示一个InspectionAttr
的符号
类型为hybrid_method
。
被赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes
定义与属性行为不同的表达行为
在前一节中,我们在Interval.contains
和Interval.intersects
方法中使用&
和|
位运算符是幸运的,考虑到我们的函数操作两个布尔值以返回一个新值。在许多情况下,一个在 Python 函数和 SQLAlchemy SQL 表达式之间有足够差异的情况下,应该定义两个单独的 Python 表达式。hybrid
装饰器为此目的定义了一个修饰符hybrid_property.expression()
。作为示例,我们将定义间隔的半径,这需要使用绝对值函数:
from sqlalchemy import ColumnElement
from sqlalchemy import Float
from sqlalchemy import func
from sqlalchemy import type_coerce
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
在上面的示例中,首先将 hybrid_property
分配给名称 Interval.radius
,然后通过后续调用名为 Interval._radius_expression
的方法,使用装饰器 @radius.inplace.expression
对其进行修改,该装饰器链接了两个修饰符 hybrid_property.inplace
和 hybrid_property.expression
。使用 hybrid_property.inplace
表示 hybrid_property.expression()
修饰符应在原地改变 Interval.radius
中的现有混合对象,而不创建新对象。关于此修饰符及其基本原理的说明在下一节中讨论 使用 inplace 创建符合 pep-484 的混合属性。使用 @classmethod
是可选的,并且严格来说是为了给出类型提示,以表明在这种情况下,cls
预期是 Interval
类,而不是 Interval
的实例。
注意
hybrid_property.inplace
以及使用 @classmethod
以获得正确的类型支持在 SQLAlchemy 2.0.4 中可用,并且在早期版本中不起作用。
现在,由于 Interval.radius
现在包含表达式元素,因此在访问类级别的 Interval.radius
时会返回 SQL 函数 ABS()
:
>>> from sqlalchemy import select
>>> print(select(Interval).filter(Interval.radius > 5))
SELECT interval.id, interval.start, interval."end"
FROM interval
WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
使用 inplace
创建符合 pep-484 的混合属性
在前一节中,演示了一个 hybrid_property
装饰器,其中包括两个单独的方法级函数被装饰,都用于生成一个名为 Interval.radius
的单个对象属性。实际上,我们可以使用几种不同的修饰符来使用 hybrid_property
,包括 hybrid_property.expression()
、hybrid_property.setter()
和 hybrid_property.update_expression()
。
SQLAlchemy 的 hybrid_property
装饰器打算通过与 Python 内置的 @property
装饰器相同的方式添加这些方法,其中惯用法是重复重新定义属性,每次使用相同的属性名称,如下面的示例所示,演示了使用 hybrid_property.setter()
和 hybrid_property.expression()
为 Interval.radius
描述符的用法:
# correct use, however is not accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self):
return abs(self.length) / 2
@radius.setter
def radius(self, value):
self.length = value * 2
@radius.expression
def radius(cls):
return type_coerce(func.abs(cls.length) / 2, Float)
如上,有三个 Interval.radius
方法,但是由于每个都被 hybrid_property
装饰器和 @radius
名称本身装饰,最终的效果是 Interval.radius
是一个包含三个不同函数的单个属性。这种用法风格来自于 Python 的@property 的文档用法。需要注意的是,无论是 @property
还是 hybrid_property
的工作方式,每次都会 复制描述符。也就是说,每次调用 @radius.expression
、@radius.setter
等都会完全创建一个新的对象。这使得属性在子类中重新定义时不会出现问题(请参阅本节稍后的 在子类之间重用混合属性 来了解如何使用)。
然而,上述方法不兼容于诸如 mypy 和 pyright 等类型工具。Python 自己的 @property
装饰器之所以没有这个限制,仅仅是因为 这些工具硬编码了@property 的行为,这意味着这种语法在 SQLAlchemy 下不符合 PEP 484 的规范。
为了在保持打字兼容的同时产生合理的语法,hybrid_property.inplace
装饰器允许同一装饰器以不同的方法名称被重复使用,同时仍然产生一个单一的装饰器在一个名称下:
# correct use which is also accepted by pep-484 tooling
class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
# for example only
self.length = value * 2
@radius.inplace.expression
@classmethod
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
使用 hybrid_property.inplace
进一步限定了装饰器的使用,不应该创建一个新的副本,从而保持了 Interval.radius
名称,同时允许额外的方法 Interval._radius_setter
和 Interval._radius_expression
使用不同的名称。
版本 2.0.4 中的新功能:添加hybrid_property.inplace
,以允许更简洁地构建复合hybrid_property
对象,同时不必重复使用方法名称。此外,允许在hybrid_property.expression
、hybrid_property.update_expression
和hybrid_property.comparator
中使用@classmethod
,以便允许类型工具将cls
识别为类而不是方法签名中的实例。
定义 Setter
hybrid_property.setter()
修饰符允许构建自定义 setter 方法,可以修改对象上的值:
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start value
现在,在设置时调用length(self, value)
方法:
>>> i1 = Interval(5, 10)
>>> i1.length
5
>>> i1.length = 12
>>> i1.end
17
允许批量 ORM 更新
当使用 ORM 启用的更新时,混合类型可以为自定义的“UPDATE”处理程序定义处理程序,允许将混合类型用于更新的 SET 子句中。
通常,当使用update()
与混合类型时,SQL 表达式将用作 SET 的目标列。如果我们的Interval
类具有链接到Interval.start
的混合类型start_point
,则可以直接替换:
from sqlalchemy import update
stmt = update(Interval).values({Interval.start_point: 10})
然而,当使用像Interval.length
这样的复合混合类型时,这个混合类型代表多于一个列。我们可以设置一个处理程序,该处理程序将适应传递到 VALUES 表达式中的值,这可能会影响此处理程序,使用hybrid_property.update_expression()
装饰器。一个与我们的 setter 类似的处理程序将是:
from typing import List, Tuple, Any
class Interval(Base):
# ...
@hybrid_property
def length(self) -> int:
return self.end - self.start
@length.inplace.setter
def _length_setter(self, value: int) -> None:
self.end = self.start value
@length.inplace.update_expression
def _length_update_expression(cls, value: Any) -> List[Tuple[Any, Any]]:
return [
(cls.end, cls.start value)
]
如果我们在 UPDATE 表达式中使用Interval.length
,我们将获得一个混合类型的 SET 表达式:
>>> from sqlalchemy import update
>>> print(update(Interval).values({Interval.length: 25}))
UPDATE interval SET "end"=(interval.start :start_1)
此 SET 表达式将由 ORM 自动处理。
另请参阅
ORM 启用的 INSERT、UPDATE 和 DELETE 语句 - 包括 ORM 启用的 UPDATE 语句的背景信息
与关系一起工作
创建与基于列的数据相反的与相关对象一起工作的混合类型时,没有本质区别。对于不同的表达式的需求往往更大。我们将说明的两种变体是“join-dependent”混合类型和“correlated subquery”混合类型。
Join-Dependent Relationship Hybrid
考虑以下将User
与SavingsAccount
相关联的声明性映射:
from __future__ import annotations
from decimal import Decimal
from typing import cast
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy import SQLColumnExpression
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Optional[Decimal]:
if self.accounts:
return self.accounts[0].balance
else:
return None
@balance.inplace.setter
def _balance_setter(self, value: Optional[Decimal]) -> None:
assert value is not None
if not self.accounts:
account = SavingsAccount(owner=self)
else:
account = self.accounts[0]
account.balance = value
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
return cast("SQLColumnExpression[Optional[Decimal]]", SavingsAccount.balance)
上述混合属性balance
与此用户的账户列表中的第一个SavingsAccount
条目配合使用。在 Python 中,getter/setter 方法可以将accounts
视为self
上可用的 Python 列表。
提示
上述示例中的User.balance
getter 访问self.acccounts
集合,通常会通过配置在User.balance
relationship()
上的selectinload()
加载策略加载。当未在relationship()
上另行说明时,默认加载策略是lazyload()
,它会按需发出 SQL。在使用 asyncio 时,不支持按需加载程序,因此在使用 asyncio 时,应确保self.accounts
集合对此混合访问器可访问。
在表达式级别,预期User
类将在适当的上下文中使用,以便存在适当的连接到SavingsAccount
:
>>> from sqlalchemy import select
>>> print(select(User, User.balance).
... join(User.accounts).filter(User.balance > 5000))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
但请注意,尽管实例级访问器需要担心self.accounts
是否存在,但在 SQL 表达式级别,这个问题表现得不同,基本上我们会使用外连接:
>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print (select(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
相关子查询关系混合
当然,我们可以放弃依赖于连接的查询使用,转而使用相关子查询,这可以方便地打包到单个列表达式中。相关子查询更具可移植性,但在 SQL 级别通常性能较差。使用与使用 column_property 中所示技术相同的技术,我们可以调整我们的SavingsAccount
示例以聚合所有账户的余额,并使用相关子查询作为列表达式:
from __future__ import annotations
from decimal import Decimal
from typing import List
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Numeric
from sqlalchemy import select
from sqlalchemy import SQLColumnExpression
from sqlalchemy import String
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Decimal:
return sum((acc.balance for acc in self.accounts), start=Decimal("0"))
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
return (
select(func.sum(SavingsAccount.balance))
.where(SavingsAccount.user_id == cls.id)
.label("total_balance")
)
上述配方将为我们提供呈现相关 SELECT 的balance
列:
>>> from sqlalchemy import select
>>> print(select(User).filter(User.balance > 400))
SELECT "user".id, "user".name
FROM "user"
WHERE (
SELECT sum(account.balance) AS sum_1 FROM account
WHERE account.user_id = "user".id
) > :param_1
连接依赖关系混合
考虑以下声明性映射,将User
与SavingsAccount
相关联:
from __future__ import annotations
from decimal import Decimal
from typing import cast
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import Numeric
from sqlalchemy import String
from sqlalchemy import SQLColumnExpression
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Optional[Decimal]:
if self.accounts:
return self.accounts[0].balance
else:
return None
@balance.inplace.setter
def _balance_setter(self, value: Optional[Decimal]) -> None:
assert value is not None
if not self.accounts:
account = SavingsAccount(owner=self)
else:
account = self.accounts[0]
account.balance = value
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Optional[Decimal]]:
return cast("SQLColumnExpression[Optional[Decimal]]", SavingsAccount.balance)
上述混合属性balance
与此用户的账户列表中的第一个SavingsAccount
条目配合使用。在 Python 中,getter/setter 方法可以将accounts
视为self
上可用的 Python 列表。
提示
上面示例中的User.balance
getter 访问了self.acccounts
集合,这通常会通过在User.balance
relationship()
上配置的selectinload()
加载器策略进行加载。当没有在其他地方声明relationship()
时,默认的加载器策略是lazyload()
,它按需发出 SQL。当使用 asyncio 时,不支持按需加载器,如lazyload()
,因此在使用 asyncio 时应注意确保self.accounts
集合对此混合访问器是可访问的。
在表达式级别,预计User
类将在适当的上下文中使用,以便存在适当的连接到SavingsAccount
:
>>> from sqlalchemy import select
>>> print(select(User, User.balance).
... join(User.accounts).filter(User.balance > 5000))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" JOIN account ON "user".id = account.user_id
WHERE account.balance > :balance_1
但请注意,尽管实例级别的访问器需要担心self.accounts
是否存在,但这个问题在 SQL 表达式级别上表现得不同,我们基本上会使用外连接:
>>> from sqlalchemy import select
>>> from sqlalchemy import or_
>>> print (select(User, User.balance).outerjoin(User.accounts).
... filter(or_(User.balance < 5000, User.balance == None)))
SELECT "user".id AS user_id, "user".name AS user_name,
account.balance AS account_balance
FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
WHERE account.balance < :balance_1 OR account.balance IS NULL
关联子查询关系混合
当然,我们可以放弃依赖包含查询中的连接,而倾向于关联子查询,这可以被封装成一个单列表达式。关联子查询更具可移植性,但在 SQL 级别上通常性能较差。使用在使用 column_property 中说明的相同技术,我们可以调整我们的SavingsAccount
示例来聚合所有账户的余额,并为列表达式使用关联子查询:
from __future__ import annotations
from decimal import Decimal
from typing import List
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Numeric
from sqlalchemy import select
from sqlalchemy import SQLColumnExpression
from sqlalchemy import String
from sqlalchemy.ext.hybrid import hybrid_property
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 SavingsAccount(Base):
__tablename__ = 'account'
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey('user.id'))
balance: Mapped[Decimal] = mapped_column(Numeric(15, 5))
owner: Mapped[User] = relationship(back_populates="accounts")
class User(Base):
__tablename__ = 'user'
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
accounts: Mapped[List[SavingsAccount]] = relationship(
back_populates="owner", lazy="selectin"
)
@hybrid_property
def balance(self) -> Decimal:
return sum((acc.balance for acc in self.accounts), start=Decimal("0"))
@balance.inplace.expression
@classmethod
def _balance_expression(cls) -> SQLColumnExpression[Decimal]:
return (
select(func.sum(SavingsAccount.balance))
.where(SavingsAccount.user_id == cls.id)
.label("total_balance")
)
上面的配方将为我们提供一个渲染关联 SELECT 的balance
列:
>>> from sqlalchemy import select
>>> print(select(User).filter(User.balance > 400))
SELECT "user".id, "user".name
FROM "user"
WHERE (
SELECT sum(account.balance) AS sum_1 FROM account
WHERE account.user_id = "user".id
) > :param_1
构建自定义比较器
混合属性还包括一个助手,允许构建自定义比较器。比较器对象允许用户单独定制每个 SQLAlchemy 表达式操作符的行为。当创建具有一些高度特殊的 SQL 端行为的自定义类型时,它们非常有用。
注意
此部分介绍的hybrid_property.comparator()
装饰器替换了hybrid_property.expression()
装饰器的使用。它们不能一起使用。
下面的示例类允许在名为word_insensitive
的属性上进行不区分大小写的比较:
from __future__ import annotations
from typing import Any
from sqlalchemy import ColumnElement
from sqlalchemy import func
from sqlalchemy.ext.hybrid import Comparator
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
class Base(DeclarativeBase):
pass
class CaseInsensitiveComparator(Comparator[str]):
def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501
return func.lower(self.__clause_element__()) == func.lower(other)
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> str:
return self.word.lower()
@word_insensitive.inplace.comparator
@classmethod
def _word_insensitive_comparator(cls) -> CaseInsensitiveComparator:
return CaseInsensitiveComparator(cls.word)
上面,针对word_insensitive
的 SQL 表达式将对双方都应用LOWER()
SQL 函数:
>>> from sqlalchemy import select
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id, searchword.word
FROM searchword
WHERE lower(searchword.word) = lower(:lower_1)
上面的CaseInsensitiveComparator
实现了ColumnOperators
接口的一部分。像小写化这样的“强制转换”操作可以应用于所有比较操作(即eq
、lt
、gt
等)使用Operators.operate()
:
class CaseInsensitiveComparator(Comparator):
def operate(self, op, other, **kwargs):
return op(
func.lower(self.__clause_element__()),
func.lower(other),
**kwargs,
)
在子类之间重用混合属性
可以从超类中引用混合,以允许修改方法,如hybrid_property.getter()
、hybrid_property.setter()
等,用于在子类上重新定义这些方法。这类似于标准的 Python @property
对象的工作方式:
class FirstNameOnly(Base):
# ...
first_name: Mapped[str]
@hybrid_property
def name(self) -> str:
return self.first_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name = value
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
# 'inplace' is not used here; calling getter creates a copy
# of FirstNameOnly.name that is local to FirstNameLastName
@FirstNameOnly.name.getter
def name(self) -> str:
return self.first_name ' ' self.last_name
@name.inplace.setter
def _name_setter(self, value: str) -> None:
self.first_name, self.last_name = value.split(' ', 1)
上面,FirstNameLastName
类引用了从FirstNameOnly.name
到混合的混合,以重新用于子类的 getter 和 setter。
当仅覆盖hybrid_property.expression()
和hybrid_property.comparator()
作为对超类的第一引用时,这些名称与在类级别返回的类级别QueryableAttribute
对象上的同名访问器发生冲突。要在直接引用父类描述符时覆盖这些方法,请添加特殊限定符hybrid_property.overrides
,它将将被仪器化的属性反向引用回混合对象:
class FirstNameLastName(FirstNameOnly):
# ...
last_name: Mapped[str]
@FirstNameOnly.name.overrides.expression
@classmethod
def name(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
混合值对象
请注意,在我们之前的示例中,如果我们将SearchWord
实例的word_insensitive
属性与普通的 Python 字符串进行比较,普通的 Python 字符串不会被强制转换为小写 - 我们构建的CaseInsensitiveComparator
,由@word_insensitive.comparator
返回,仅适用于 SQL 端。
自定义比较器的更全面形式是构建一个混合值对象。这种技术将目标值或表达式应用于一个值对象,然后该值对象在所有情况下由访问器返回。值对象允许控制对值的所有操作以及如何处理比较值,无论是在 SQL 表达式端还是 Python 值端。用新的CaseInsensitiveWord
类替换之前的CaseInsensitiveComparator
类:
class CaseInsensitiveWord(Comparator):
"Hybrid value representing a lower case representation of a word."
def __init__(self, word):
if isinstance(word, basestring):
self.word = word.lower()
elif isinstance(word, CaseInsensitiveWord):
self.word = word.word
else:
self.word = func.lower(word)
def operate(self, op, other, **kwargs):
if not isinstance(other, CaseInsensitiveWord):
other = CaseInsensitiveWord(other)
return op(self.word, other.word, **kwargs)
def __clause_element__(self):
return self.word
def __str__(self):
return self.word
key = 'word'
"Label to apply to Query tuple results"
在上文中,CaseInsensitiveWord
对象表示 self.word
,它可能是一个 SQL 函数,也可能是一个 Python 本地函数。通过重写 operate()
和 __clause_element__()
方法,以 self.word
为基础进行操作,所有比较操作都将针对 word
的“转换”形式进行,无论是在 SQL 还是 Python 方面。我们的 SearchWord
类现在可以通过单一的混合调用无条件地提供 CaseInsensitiveWord
对象:
class SearchWord(Base):
__tablename__ = 'searchword'
id: Mapped[int] = mapped_column(primary_key=True)
word: Mapped[str]
@hybrid_property
def word_insensitive(self) -> CaseInsensitiveWord:
return CaseInsensitiveWord(self.word)
word_insensitive
属性现在具有普遍的不区分大小写的比较行为,包括 SQL 表达式与 Python 表达式(请注意此处的 Python 值在 Python 侧被转换为小写):
>>> print(select(SearchWord).filter_by(word_insensitive="Trucks"))
SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
FROM searchword
WHERE lower(searchword.word) = :lower_1
SQL 表达式与 SQL 表达式:
代码语言:javascript复制>>> from sqlalchemy.orm import aliased
>>> sw1 = aliased(SearchWord)
>>> sw2 = aliased(SearchWord)
>>> print(
... select(sw1.word_insensitive, sw2.word_insensitive).filter(
... sw1.word_insensitive > sw2.word_insensitive
... )
... )
SELECT lower(searchword_1.word) AS lower_1,
lower(searchword_2.word) AS lower_2
FROM searchword AS searchword_1, searchword AS searchword_2
WHERE lower(searchword_1.word) > lower(searchword_2.word)
Python 只有表达式:
代码语言:javascript复制>>> ws1 = SearchWord(word="SomeWord")
>>> ws1.word_insensitive == "sOmEwOrD"
True
>>> ws1.word_insensitive == "XOmEwOrX"
False
>>> print(ws1.word_insensitive)
someword
混合值模式对于任何可能具有多个表示形式的值非常有用,例如时间戳、时间间隔、测量单位、货币和加密密码等。
另请参阅
混合类型和值无关类型 - 在 techspot.zzzeek.org 博客上
值无关类型,第二部分 - 在 techspot.zzzeek.org 博客上
API 参考
对象名称 | 描述 |
---|---|
Comparator | 一个辅助类,允许轻松构建用于混合类型的自定义 PropComparator 类。 |
hybrid_method | 允许定义具有实例级和类级行为的 Python 对象方法的装饰器。 |
hybrid_property | 允许定义具有实例级和类级行为的 Python 描述符的装饰器。 |
HybridExtensionType | 枚举类型。 |
class sqlalchemy.ext.hybrid.hybrid_method
允许定义具有实例级和类级行为的 Python 对象方法的装饰器。
成员
init(), expression(), extension_type, inplace, is_attribute
类签名
class sqlalchemy.ext.hybrid.hybrid_method
(sqlalchemy.orm.base.InspectionAttrInfo
, typing.Generic
)
method __init__(func: Callable[[Concatenate[Any, _P]], _R], expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]] | None = None)
创建一个新的 hybrid_method
。
通常使用装饰器:
代码语言:javascript复制from sqlalchemy.ext.hybrid import hybrid_method
class SomeClass:
@hybrid_method
def value(self, x, y):
return self._value x y
@value.expression
@classmethod
def value(cls, x, y):
return func.some_function(cls._value, x, y)
代码语言:javascript复制method expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) → hybrid_method[_P, _R]
提供一个修改装饰器,定义一个生成 SQL 表达式的方法。
代码语言:javascript复制attribute extension_type: InspectionAttrExtensionType = 'HYBRID_METHOD'
扩展类型(如果有)。默认为 NotExtension.NOT_EXTENSION
另请参阅
HybridExtensionType
AssociationProxyExtensionType
attribute inplace
返回此 hybrid_method
的 inplace mutator。
当调用 hybrid_method.expression()
装饰器时,hybrid_method
类已经执行“in place”变异,因此此属性返回 Self。
2.0.4 版中的新功能。
另请参阅
使用 inplace 创建符合 pep-484 标准的混合属性(#hybrid-pep484-naming)
代码语言:javascript复制attribute is_attribute = True
如果此对象是 Python 描述符,则为 True。
这可能是许多类型之一。通常是一个 QueryableAttribute
,它代表一个 MapperProperty
上的属性事件。但也可以是一个扩展类型,例如 AssociationProxy
或 hybrid_property
。InspectionAttr.extension_type
将指示一个常量,用于标识特定的子类型。
另请参阅
Mapper.all_orm_descriptors
class sqlalchemy.ext.hybrid.hybrid_property
一个装饰器,允许定义既有实例级别又有类级别行为的 Python 描述符。
成员
init(), comparator(), deleter(), expression(), extension_type, getter(), inplace, is_attribute, overrides, setter(), update_expression()
类签名
类sqlalchemy.ext.hybrid.hybrid_property
(sqlalchemy.orm.base.InspectionAttrInfo
, sqlalchemy.orm.base.ORMDescriptor
)
method __init__(fget: _HybridGetterType[_T], fset: _HybridSetterType[_T] | None = None, fdel: _HybridDeleterType[_T] | None = None, expr: _HybridExprCallableType[_T] | None = None, custom_comparator: Comparator[_T] | None = None, update_expr: _HybridUpdaterType[_T] | None = None)
创建一个新的hybrid_property
。
通常通过装饰器来使用:
代码语言:javascript复制from sqlalchemy.ext.hybrid import hybrid_property
class SomeClass:
@hybrid_property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
代码语言:javascript复制method comparator(comparator: _HybridComparatorCallableType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个自定义比较器生成方法。
被装饰方法的返回值应该是Comparator
的一个实例。
注意
hybrid_property.comparator()
装饰器替换了hybrid_property.expression()
装饰器的使用。它们不能同时使用。
当在类级别调用混合属性时,此处给出的Comparator
对象被包装在一个专门的QueryableAttribute
中,这是 ORM 用来表示其他映射属性的相同类型的对象。这样做的原因是为了在返回的结构中保留其他类级别属性,如文档字符串和对混合属性本身的引用,而不对传入的原始比较器对象进行任何修改。
注意
当从拥有类引用混合属性时(例如SomeClass.some_hybrid
),返回一个QueryableAttribute
的实例,表示表达式或比较器对象作为这个混合对象。然而,该对象本身有名为expression
和comparator
的访问器;因此,在子类中尝试覆盖这些装饰器时,可能需要首先使用hybrid_property.overrides
修饰符进行限定。有关详细信息,请参阅该修饰符。
method deleter(fdel: _HybridDeleterType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个删除方法。
代码语言:javascript复制method expression(expr: _HybridExprCallableType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个生成 SQL 表达式的方法。
当在类级别调用混合时,此处给出的 SQL 表达式将包装在一个专门的 QueryableAttribute
中,该对象与 ORM 用于表示其他映射属性的对象相同。这样做的原因是为了在返回的结构中保持其他类级别属性(如文档字符串和对混合本身的引用),而不对传入的原始 SQL 表达式进行任何修改。
注意
当从拥有类引用混合属性时(例如 SomeClass.some_hybrid
),会返回一个 QueryableAttribute
的实例,表示表达式或比较器对象以及此混合对象。然而,该对象本身有名为 expression
和 comparator
的访问器;因此,在尝试在子类上覆盖这些装饰器时,可能需要首先使用 hybrid_property.overrides
修饰符进行限定。详情请参阅该修饰符。
参见
定义与属性行为不同的表达式行为
代码语言:javascript复制attribute extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'
扩展类型,如果有的话。默认为 NotExtension.NOT_EXTENSION
参见
HybridExtensionType
AssociationProxyExtensionType
method getter(fget: _HybridGetterType[_T]) → hybrid_property[_T]
提供一个修改装饰器,定义一个 getter 方法。
自 1.2 版新功能。
代码语言:javascript复制attribute inplace
返回此 hybrid_property
的 inplace mutator。
这是为了允许对混合进行原地变异,从而允许重用某个特定名称的第一个混合方法以添加更多方法,而无需将这些方法命名为相同的名称,例如:
代码语言:javascript复制class Interval(Base):
# ...
@hybrid_property
def radius(self) -> float:
return abs(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
self.length = value * 2
@radius.inplace.expression
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
自 2.0.4 版新功能。
参见
使用 inplace 创建符合 pep-484 的混合属性
代码语言:javascript复制attribute is_attribute = True
如果此对象是 Python 描述符,则为 True。
这可以指代许多类型之一。通常是一个 QueryableAttribute
,它代表一个 MapperProperty
的属性事件。但也可以是一个扩展类型,如 AssociationProxy
或 hybrid_property
。InspectionAttr.extension_type
将引用一个常量,用于标识特定的子类型。
另请参见
Mapper.all_orm_descriptors
attribute overrides
用于覆盖现有属性的方法的前缀。
hybrid_property.overrides
访问器只是返回这个混合对象,当在父类的类级别调用时,将取消引用通常在此级别返回的“instrumented attribute”,并允许修改装饰器,如 hybrid_property.expression()
和 hybrid_property.comparator()
被使用,而不会与通常存在于 QueryableAttribute
上的同名属性发生冲突:
class SuperClass:
# ...
@hybrid_property
def foobar(self):
return self._foobar
class SubClass(SuperClass):
# ...
@SuperClass.foobar.overrides.expression
def foobar(cls):
return func.subfoobar(self._foobar)
版本 1.2 中新增。
另请参见
在子类中重用混合属性
代码语言:javascript复制method setter(fset: _HybridSetterType[_T]) → hybrid_property[_T]
提供一个定义 setter 方法的修改装饰器。
代码语言:javascript复制method update_expression(meth: _HybridUpdaterType[_T]) → hybrid_property[_T]
提供一个定义 UPDATE 元组生成方法的修改装饰器。
该方法接受一个值,该值将被渲染到 UPDATE 语句的 SET 子句中。然后该方法应将此值处理为适合最终 SET 子句的单独列表达式,并将它们作为 2 元组序列返回。每个元组包含一个列表达式作为键和要渲染的值。
例如:
代码语言:javascript复制class Person(Base):
# ...
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def fullname(self):
return first_name " " last_name
@fullname.update_expression
def fullname(cls, value):
fname, lname = value.split(" ", 1)
return [
(cls.first_name, fname),
(cls.last_name, lname)
]
版本 1.2 中新增。
代码语言:javascript复制class sqlalchemy.ext.hybrid.Comparator
一个辅助类,允许轻松构建用于混合使用的自定义 PropComparator
类。
类签名
class sqlalchemy.ext.hybrid.Comparator
(sqlalchemy.orm.PropComparator
)
class sqlalchemy.ext.hybrid.HybridExtensionType
一个枚举。
成员
HYBRID_METHOD, HYBRID_PROPERTY
类签名
类sqlalchemy.ext.hybrid.HybridExtensionType
(sqlalchemy.orm.base.InspectionAttrExtensionType
)
attribute HYBRID_METHOD = 'HYBRID_METHOD'
表示一个InspectionAttr
的符号,类型为hybrid_method
赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes
attribute HYBRID_PROPERTY = 'HYBRID_PROPERTY'
表示一个InspectionAttr
的符号
类型为hybrid_method
。
赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes
(self.length) / 2
@radius.inplace.setter
def _radius_setter(self, value: float) -> None:
self.length = value * 2
@radius.inplace.expression
def _radius_expression(cls) -> ColumnElement[float]:
return type_coerce(func.abs(cls.length) / 2, Float)
代码语言:javascript复制自 2.0.4 版新功能。
参见
使用 inplace 创建符合 pep-484 的混合属性
```py
attribute is_attribute = True
如果此对象是 Python 描述符,则为 True。
这可以指代许多类型之一。通常是一个 QueryableAttribute
,它代表一个 MapperProperty
的属性事件。但也可以是一个扩展类型,如 AssociationProxy
或 hybrid_property
。InspectionAttr.extension_type
将引用一个常量,用于标识特定的子类型。
另请参见
Mapper.all_orm_descriptors
attribute overrides
用于覆盖现有属性的方法的前缀。
hybrid_property.overrides
访问器只是返回这个混合对象,当在父类的类级别调用时,将取消引用通常在此级别返回的“instrumented attribute”,并允许修改装饰器,如 hybrid_property.expression()
和 hybrid_property.comparator()
被使用,而不会与通常存在于 QueryableAttribute
上的同名属性发生冲突:
class SuperClass:
# ...
@hybrid_property
def foobar(self):
return self._foobar
class SubClass(SuperClass):
# ...
@SuperClass.foobar.overrides.expression
def foobar(cls):
return func.subfoobar(self._foobar)
版本 1.2 中新增。
另请参见
在子类中重用混合属性
代码语言:javascript复制method setter(fset: _HybridSetterType[_T]) → hybrid_property[_T]
提供一个定义 setter 方法的修改装饰器。
代码语言:javascript复制method update_expression(meth: _HybridUpdaterType[_T]) → hybrid_property[_T]
提供一个定义 UPDATE 元组生成方法的修改装饰器。
该方法接受一个值,该值将被渲染到 UPDATE 语句的 SET 子句中。然后该方法应将此值处理为适合最终 SET 子句的单独列表达式,并将它们作为 2 元组序列返回。每个元组包含一个列表达式作为键和要渲染的值。
例如:
代码语言:javascript复制class Person(Base):
# ...
first_name = Column(String)
last_name = Column(String)
@hybrid_property
def fullname(self):
return first_name " " last_name
@fullname.update_expression
def fullname(cls, value):
fname, lname = value.split(" ", 1)
return [
(cls.first_name, fname),
(cls.last_name, lname)
]
版本 1.2 中新增。
代码语言:javascript复制class sqlalchemy.ext.hybrid.Comparator
一个辅助类,允许轻松构建用于混合使用的自定义 PropComparator
类。
类签名
class sqlalchemy.ext.hybrid.Comparator
(sqlalchemy.orm.PropComparator
)
class sqlalchemy.ext.hybrid.HybridExtensionType
一个枚举。
成员
HYBRID_METHOD, HYBRID_PROPERTY
类签名
类sqlalchemy.ext.hybrid.HybridExtensionType
(sqlalchemy.orm.base.InspectionAttrExtensionType
)
attribute HYBRID_METHOD = 'HYBRID_METHOD'
表示一个InspectionAttr
的符号,类型为hybrid_method
赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes
attribute HYBRID_PROPERTY = 'HYBRID_PROPERTY'
表示一个InspectionAttr
的符号
类型为hybrid_method
。
赋予InspectionAttr.extension_type
属性。
另请参阅
Mapper.all_orm_attributes