更改 Attribute 行为


本节将讨论用于修改 ORM 映射属性的行为,包括那些映射有 mapped_column()、relationship() 等。


简单验证器


将 “validation” 例程添加到属性的一种快速方法是使用 validates() 装饰器。属性验证器可以引发异常,停止更改属性值的过程,或者可以将给定的值更改为不同的值。验证器与所有属性扩展一样,仅由普通用户空间代码调用;当 ORM 填充对象时,不会发出它们:

from sqlalchemy.orm import validates


class EmailAddress(Base):
    __tablename__ = "address"

    id = mapped_column(Integer, primary_key=True)
    email = mapped_column(String)

    @validates("email")
    def validate_email(self, key, address):
        if "@" not in address:
            raise ValueError("failed simple email validation")
        return address


当项目被添加到集合中时,验证器还会收到集合附加事件:

from sqlalchemy.orm import validates


class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses")
    def validate_address(self, key, address):
        if "@" not in address.email:
            raise ValueError("failed simplified email validation")
        return address


默认情况下,不会为集合删除事件发出验证函数,因为典型的预期是被丢弃的值不需要验证。但是, validates() 通过向装饰器指定 include_removes=True 来支持接收这些事件。设置此标志后,验证函数必须接收一个额外的布尔参数,如果 True 表示该作是删除:

from sqlalchemy.orm import validates


class User(Base):
    # ...

    addresses = relationship("Address")

    @validates("addresses", include_removes=True)
    def validate_address(self, key, address, is_remove):
        if is_remove:
            raise ValueError("not allowed to remove items from the collection")
        else:
            if "@" not in address.email:
                raise ValueError("failed simplified email validation")
            return address


也可以使用 include_backrefs=False 选项定制通过 backref 链接相互依赖的验证器的情况;当设置为 False 时,如果事件是由于 backref 而发生的,则阻止验证函数发出:

from sqlalchemy.orm import validates


class User(Base):
    # ...

    addresses = relationship("Address", backref="user")

    @validates("addresses", include_backrefs=False)
    def validate_address(self, key, address):
        if "@" not in address:
            raise ValueError("failed simplified email validation")
        return address


在上面,如果我们像 some_address.user = some_user 那样分配给 Address.user,则不会发出 validate_address() 函数,即使 some_user.addresses 发生了 append - 该事件是由 backref 引起的。


请注意,validates() 装饰器是构建在 attribute events 之上的便捷函数。需要对属性更改行为的配置进行更多控制的应用程序可以使用此系统,如 AttributeEvents 中所述。


对象名称

描述


validates(*names, [include_removes, include_backrefs])


将方法装饰为一个或多个命名属性的 'validator'。


函数 sqlalchemy.orm 中。validates*names strinclude_removes: bool = Falseinclude_backrefs bool = True Callable[[_Fn] _Fn]


将方法装饰为一个或多个命名属性的 'validator'。


将方法指定为验证器,该方法接收属性的名称以及要分配的值,或者在集合的情况下,指定要添加到集合的值。然后,该函数可以引发验证异常以停止进程继续(其中 Python 内置的 ValueErrorAssertionError 异常是合理的选择),或者可以在继续之前修改或替换值。否则,该函数应返回给定的值。


请注意,集合的验证器无法在验证例程中发出该集合的加载 - 此用法会引发断言以避免递归溢出。这是不支持的可重入条件。


参数

  • names – 要验证的属性名称列表。


  • include_removes- 如果为 True,则还将发送 “remove” 事件 - 验证函数必须接受一个额外的参数 “is_remove”,该参数将是一个布尔值。

  • include_backrefs


    默认为 True;如果为 False,则 如果发起方是属性,则验证函数不会发出 event 相关的 EVENT。 这可用于双向 validates() 用法,其中每个属性作只应发出一个验证器。


    在 2.0.16 版本发生变更: 此参数无意中默认为 对于版本 2.0.0 到 2.0.15,为 False。其正确的默认值 True 在 2.0.16 中恢复。


另请参阅


简单验证器 - validates() 的使用示例


在核心级别使用自定义数据类型


通过使用应用于映射的 Table 元数据的自定义数据类型,可以实现一种非 ORM 方法,即以适合在 Python 中的表示方式与数据库中的表示方式之间转换数据的方式影响列的值。这在某种编码/解码样式的情况下更为常见,这种编码/解码在数据进入数据库和返回数据时都会发生;在 Augmenting Existing Types 的核心文档中阅读更多相关信息。


使用描述符和 hybrid


为属性生成修改行为的更全面的方法是使用 Descriptors。这些在 Python 中通常使用 property() 功能。描述符的标准 SQLAlchemy 技术是创建一个 plain 描述符,并使其从具有 不同的名称。下面我们使用 Python 2.6 样式的属性来说明这一点:

class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    # name the attribute with an underscore,
    # different from the column name
    _email = mapped_column("email", String)

    # then create an ".email" attribute
    # to get/set "._email"
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email


上述方法将起作用,但我们可以添加更多内容。虽然我们的 EmailAddress 对象将通过电子邮件传递值 descriptor 并放入 _email 映射属性中,类级别 EmailAddress.email 属性没有可用于 Select 的常用表达式语义。为了提供这些,我们改用 混合扩展,如下所示:

from sqlalchemy.ext.hybrid import hybrid_property


class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
        return self._email

    @email.setter
    def email(self, email):
        self._email = email


.email 属性除了在我们拥有 EmailAddress 实例时提供 getter/setter 行为外,还在类级别(即直接从 EmailAddress 类)使用时提供 SQL 表达式:

from sqlalchemy.orm import Session
from sqlalchemy import select

session = Session()

address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address@example.com")
).one()
SELECT address.email AS address_email, address.id AS address_id FROM address WHERE address.email = ? ('address@example.com',)
address.email = "otheraddress@example.com" session.commit()
UPDATE address SET email=? WHERE address.id = ? ('otheraddress@example.com', 1) COMMIT


hybrid_property还允许我们更改 属性,包括在属性为 在实例级别访问,而不是在类/表达式级别访问,使用 hybrid_property.expression() 修饰符。例如,如果我们想自动添加主机名,我们可以定义两组字符串作逻辑:

class EmailAddress(Base):
    __tablename__ = "email_address"

    id = mapped_column(Integer, primary_key=True)

    _email = mapped_column("email", String)

    @hybrid_property
    def email(self):
        """Return the value of _email up until the last twelve
        characters."""

        return self._email[:-12]

    @email.setter
    def email(self, email):
        """Set the value of _email, tacking on the twelve character
        value @example.com."""

        self._email = email + "@example.com"

    @email.expression
    def email(cls):
        """Produce a SQL expression that represents the value
        of the _email column, minus the last twelve characters."""

        return func.substr(cls._email, 0, func.length(cls._email) - 12)


在上面,访问 EmailAddress 实例的 email 属性 将返回 _email 属性的值,从该值中删除或添加主机名 @example.com。当我们针对电子邮件进行查询时 attribute 时,会渲染一个 SQL 函数,它会产生相同的效果:

address = session.scalars(
    select(EmailAddress).where(EmailAddress.email == "address")
).one()
SELECT address.email AS address_email, address.id AS address_id FROM address WHERE substr(address.email, ?, length(address.email) - ?) = ? (0, 12, 'address')


Hybrid Attributes 上阅读有关混合动力车的更多信息。


同义词


同义词是映射器级别的构造,它允许类上的任何属性“镜像”映射的另一个属性。


从最基本的意义上讲,同义词是一种通过附加名称使特定属性可用的简单方法:

from sqlalchemy.orm import synonym


class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    job_status = mapped_column(String(50))

    status = synonym("job_status")


上面的类 MyClass 有两个属性,.job_status.status 将作为一个属性运行,两者都在表达式级别:

>>> print(MyClass.job_status == "some_status")
my_table.job_status = :job_status_1
>>> print(MyClass.status == "some_status")
my_table.job_status = :job_status_1


在实例级别:

>>> m1 = MyClass(status="x")
>>> m1.status, m1.job_status
('x', 'x')

>>> m1.job_status = "y"
>>> m1.status, m1.job_status
('y', 'y')


synonym() 可用于子类 MapperProperty 的任何类型的 Map 属性,包括 Map 列和关系,以及同义词本身。


除了简单的镜像之外,synonym() 还可以用于引用用户定义的 Descriptors。 我们可以提供我们的 status 同义词 @property

class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @property
    def job_status(self):
        return "Status: " + self.status

    job_status = synonym("status", descriptor=job_status)


当使用 Declare 时,可以使用 synonym_for() 装饰器更简洁地表达上述模式:

from sqlalchemy.ext.declarative import synonym_for


class MyClass(Base):
    __tablename__ = "my_table"

    id = mapped_column(Integer, primary_key=True)
    status = mapped_column(String(50))

    @synonym_for("status")
    @property
    def job_status(self):
        return "Status: " + self.status


虽然 synonym() 对于简单镜像很有用,但在现代使用中使用 hybrid attribute 功能可以更好地处理使用 Descriptors 增强属性行为的用例,该功能更面向 Python Descriptors。从技术上讲,同义词() 可以执行 hybrid_property 可以执行的所有作,因为它还支持注入自定义 SQL 功能,但混合在更复杂的情况下使用更直接。


对象名称

描述


同义词(名称、*、[map_column、描述符、comparator_factory、init、repr、default、default_factory、比较、kw_only、哈希、信息、文档])


将属性名称表示为映射属性的同义词,因为该属性将反映另一个属性的值和表达式行为。


函数 sqlalchemy.orm 中。同义词name str*, map_column:boolNone=None, descriptor:AnyNone=None, comparator_factory:Type[PropComparator[_T]]None=None, init:_NoArgbool=_NoArg.NO_ARG, repr:_NoArgbool=_NoArg.NO_ARG, default:_NoArg_T=_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) 同义词[Any]


将属性名称表示为映射属性的同义词,因为该属性将反映另一个属性的值和表达式行为。


例如:

class MyClass(Base):
    __tablename__ = "my_table"

    id = Column(Integer, primary_key=True)
    job_status = Column(String(50))

    status = synonym("job_status")

参数

  • name- 现有 mapped 属性的名称。这可以引用在类上配置的字符串名称 ORM-mapped 属性,包括列绑定属性和关系。


  • descriptor– 一个 Python 描述符,当在实例级别访问此属性时,它将用作 getter(也可能是一个 setter)。

  • map_column


    对于经典映射和针对 仅现有 Table 对象。如果为 True,则 synonym() construct 将找到 Column 对象 表,该表通常与 this 同义词,并生成一个新的 ColumnProperty,该 ColumnProperty 将 this Column 映射 更改为作为 “name” 的备用名称 同义词的参数;这样,重新定义的通常步骤 的映射 To be under a different name 是 必要。这通常用于 替换为同样使用 描述符,即与 synonym.descriptor 参数:

    my_table = Table(
        "my_table",
        metadata,
        Column("id", Integer, primary_key=True),
        Column("job_status", String(50)),
    )
    
    
    class MyClass:
        @property
        def _job_status_descriptor(self):
            return "Status: %s" % self._job_status
    
    
    mapper(
        MyClass,
        my_table,
        properties={
            "job_status": synonym(
                "_job_status",
                map_column=True,
                descriptor=MyClass._job_status_descriptor,
            )
        },
    )


    在上面,名为 _job_status 的属性会自动映射到 job_status 列:

    >>> j1 = MyClass()
    >>> j1._job_status = "employed"
    >>> j1.job_status
    Status: employed


    当使用 Declare 时,为了在 与同义词结合使用时,请使用 sqlalchemy.ext.declarative.synonym_for() 助手。但是,请注意,通常应首选 hybrid properties 功能,尤其是在重新定义属性行为时。


  • info– 可选的数据字典,将被填充到 InspectionAttr.info 对象的属性。

  • comparator_factory


    PropComparator 的子类 ,它将在 SQL 表达式中提供自定义比较行为 水平。


    注意


    对于提供重新定义两者的属性的用例 属性的 Python 级和 SQL 表达式级行为, 请参考 Hybrid 属性 使用 Descriptors 和 Hybrids 以获得更有效的技术。


另请参阅


同义词 - 同义词概述


synonym_for() - 面向声明式的帮助程序


使用描述符和混合 - 混合属性扩展提供了一种更新的方法,与使用同义词相比,可以更灵活地增强属性行为。


Operator 自定义


SQLAlchemy ORM 和 Core 表达式语言使用的 “运算符” 是完全可定制的。 例如,比较表达式 User.name == 'ed' 使用 Python 本身内置的名为 operator.eq 的运算符 - 可以修改 SQLAlchemy 与此类运算符关联的实际 SQL 结构。新作也可以与列表达式相关联。列表达式的运算符在类型级别最直接地重新定义 - 有关说明,请参阅 重新定义和创建新运算符 部分。


ORM 级别的函数,如 column_property()、relationship()composite() 也通过将 PropComparator 子类传递给 ORM 级别来提供运算符重新定义comparator_factory 参数。 此级别的运算符自定义是一个 罕见用例。 请参阅 PropComparator 上的文档 了解概述。