更改 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
中所述。
对象名称 |
描述 |
---|---|
|
|
-
函数 sqlalchemy.orm 中。validates(*names: str, include_removes: bool = False, include_backrefs: bool = True)Callable[[_Fn], _Fn] ¶
将方法装饰为一个或多个命名属性的 'validator'。
将方法指定为验证器,该方法接收属性的名称以及要分配的值,或者在集合的情况下,指定要添加到集合的值。然后,该函数可以引发验证异常以停止进程继续(其中 Python 内置的ValueError
和AssertionError
异常是合理的选择),或者可以在继续之前修改或替换值。否则,该函数应返回给定的值。
请注意,集合的验证器无法在验证例程中发出该集合的加载 - 此用法会引发断言以避免递归溢出。这是不支持的可重入条件。
参数
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 功能,但混合在更复杂的情况下使用更直接。
对象名称 |
描述 |
---|---|
|
|
-
函数 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
将 thisColumn
映射 更改为作为 “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 以获得更有效的技术。
Operator 自定义¶
SQLAlchemy ORM 和 Core 表达式语言使用的 “运算符”
是完全可定制的。 例如,比较表达式
User.name == 'ed'
使用 Python 本身内置的名为 operator.eq
的运算符 - 可以修改 SQLAlchemy 与此类运算符关联的实际 SQL 结构。新作也可以与列表达式相关联。列表达式的运算符在类型级别最直接地重新定义 - 有关说明,请参阅 重新定义和创建新运算符 部分。
ORM 级别的函数,如 column_property()、
relationship()
和 composite()
也通过将 PropComparator
子类传递给 ORM
级别来提供运算符重新定义comparator_factory
参数。 此级别的运算符自定义是一个
罕见用例。 请参阅 PropComparator
上的文档
了解概述。