SqlAlchemy 2.0 中文文档(三十二)(3)

简介: SqlAlchemy 2.0 中文文档(三十二)

SqlAlchemy 2.0 中文文档(三十二)(2)https://developer.aliyun.com/article/1562543


混合值对象

请注意,在我们之前的例子中,如果我们将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 表达式:

>>> 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 表达式:

>>> 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_methodsqlalchemy.orm.base.InspectionAttrInfo, typing.Generic)

method __init__(func: Callable[[Concatenate[Any, _P]], _R], expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]] | None = None)

创建一个新的hybrid_method

通常使用装饰器:

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)
method expression(expr: Callable[[Concatenate[Any, _P]], SQLCoreOperations[_R]]) → hybrid_method[_P, _R]

提供一个修改装饰器,定义一个生成 SQL 表达式的方法。

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 的混合属性

attribute is_attribute = True

如果这个对象是一个 Python 描述符,则为 True。

这可以指代许多类型之一。通常是一个处理属性事件的QueryableAttribute,代表一个MapperProperty。但也可以是一个扩展类型,如AssociationProxyhybrid_propertyInspectionAttr.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_propertysqlalchemy.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

通常使用修饰器:

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
method comparator(comparator: _HybridComparatorCallableType[_T]) → hybrid_property[_T]

提供一个修饰器,定义一个自定义比较器生成方法。

被修饰方法的返回值应该是Comparator的一个实例。

注意

hybrid_property.comparator()修饰器替代hybrid_property.expression()修饰器的使用。它们不能同时使用。

当在类级别调用混合属性时,这里给出的Comparator对象被包装在一个专门的QueryableAttribute中,这是 ORM 用来表示其他映射属性的对象。这样做的原因是为了在返回的结构中保留其他类级别属性,如文档字符串和对混合属性本身的引用,而不对传入的原始比较器对象进行任何修改。

注意

当引用拥有类(例如 SomeClass.some_hybrid)的混合属性时,会返回一个QueryableAttribute的实例,表示此混合对象的表达式或比较器对象。但是,该对象本身具有名为 expressioncomparator 的访问器;因此,在尝试在子类上覆盖这些装饰器时,可能需要首先使用 hybrid_property.overrides 修饰符进行限定。有关详细信息,请参阅该修饰符。

method deleter(fdel: _HybridDeleterType[_T]) → hybrid_property[_T]

提供一个修改装饰器,定义一个删除方法。

method expression(expr: _HybridExprCallableType[_T]) → hybrid_property[_T]

提供一个修改装饰器,定义一个生成 SQL 表达式的方法。

当在类级别调用混合时,此处给出的 SQL 表达式将包装在一个专门的 QueryableAttribute 内,这是 ORM 用于表示其他映射属性的相同类型的对象。这样做的原因是为了在返回的结构中维护其他类级别属性,例如文档字符串和混合本身的引用,而不对传入的原始 SQL 表达式进行任何修改。

注意

当引用拥有类(例如 SomeClass.some_hybrid)的混合属性时,会返回一个QueryableAttribute的实例,表示表达式或比较器对象以及此混合对象。但是,该对象本身具有名为 expressioncomparator 的访问器;因此,在尝试在子类上覆盖这些装饰器时,可能需要首先使用 hybrid_property.overrides 修饰符进行限定。有关详细信息,请参阅该修饰符。

请参见

定义与属性行为不同的表达行为

attribute extension_type: InspectionAttrExtensionType = 'HYBRID_PROPERTY'

扩展类型,如果有的话。默认为 NotExtension.NOT_EXTENSION

请参见

HybridExtensionType

AssociationProxyExtensionType

method getter(fget: _HybridGetterType[_T]) → hybrid_property[_T]

提供一个修改装饰器,定义一个获取器方法。

自 1.2 版开始新添加。

attribute inplace

返回此 hybrid_property 的原地变异器。

这是为了允许对混合进行就地变异,允许重用特定名称的第一个混合方法以添加更多方法,而不必将这些方法命名为相同的名称,例如:

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 的混合属性

attribute is_attribute = True

如果此对象是 Python 的 描述符,则为 True。

这可能是多种类型之一。通常是一个QueryableAttribute,它代表一个MapperProperty上的属性事件。但也可以是诸如AssociationProxyhybrid_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 版的新功能。

另请参阅

在子类中重用混合属性

method setter(fset: _HybridSetterType[_T]) → hybrid_property[_T]

提供一个定义 setter 方法的修改装饰器。

method update_expression(meth: _HybridUpdaterType[_T]) → hybrid_property[_T]

提供一个定义产生 UPDATE 元组的修改装饰器方法。

该方法接受一个值,该值将被渲染到 UPDATE 语句的 SET 子句中。然后,该方法应将此值处理为适合最终 SET 子句的单个列表达式,并将它们作为 2 元组的序列返回。每个元组包含一个列表达式作为键和要渲染的值。

例如:

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 版的新功能。

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.containsInterval.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.inplacehybrid_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_setterInterval._radius_expression 使用不同的名称。

版本 2.0.4 中的新功能:添加hybrid_property.inplace,以允许更简洁地构建复合hybrid_property对象,同时不必重复使用方法名称。此外,允许在hybrid_property.expressionhybrid_property.update_expressionhybrid_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 语句的背景信息


SqlAlchemy 2.0 中文文档(三十二)(4)https://developer.aliyun.com/article/1562554

相关文章
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(二十九)(2)
SqlAlchemy 2.0 中文文档(二十九)
34 7
|
3月前
|
SQL 存储 关系型数据库
SqlAlchemy 2.0 中文文档(三十四)(4)
SqlAlchemy 2.0 中文文档(三十四)
37 1
|
3月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(三十四)(5)
SqlAlchemy 2.0 中文文档(三十四)
35 0
|
3月前
|
存储 SQL 测试技术
SqlAlchemy 2.0 中文文档(三十一)(1)
SqlAlchemy 2.0 中文文档(三十一)
29 1
|
3月前
|
SQL 测试技术 数据库
SqlAlchemy 2.0 中文文档(三十一)(2)
SqlAlchemy 2.0 中文文档(三十一)
24 1
|
3月前
|
SQL 数据库 Python
SqlAlchemy 2.0 中文文档(三十一)(3)
SqlAlchemy 2.0 中文文档(三十一)
23 1
|
3月前
|
JSON 测试技术 数据格式
SqlAlchemy 2.0 中文文档(三十一)(4)
SqlAlchemy 2.0 中文文档(三十一)
31 1
|
3月前
|
SQL API 数据安全/隐私保护
SqlAlchemy 2.0 中文文档(三十二)(5)
SqlAlchemy 2.0 中文文档(三十二)
25 0
|
3月前
|
存储 SQL 数据库
SqlAlchemy 2.0 中文文档(三十二)(1)
SqlAlchemy 2.0 中文文档(三十二)
14 0
|
3月前
|
SQL Python
SqlAlchemy 2.0 中文文档(三十二)(2)
SqlAlchemy 2.0 中文文档(三十二)
18 0