SQLAlchemy 2.0 文档
- 上一章: SQL 表达式作为映射属性
- 下一章: 复合列类型
- 上一级: 首页
- 本页内容
更改属性行为¶
本节将讨论用于修改 ORM 映射属性行为的功能和技术,包括使用 mapped_column()
、relationship()
等映射的属性。
简单验证器¶
使用 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
选项,还可以调整通过反向引用链接的相互依赖的验证器的情况;当此选项设置为 False
时,如果事件是由反向引用引起的,则会阻止验证函数发出。当使用双向 validates()
时,可以使用此选项确保每次属性操作只发出一个验证器。
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
中发生追加 - 此事件是由反向引用引起的。
请注意,validates()
装饰器是在属性事件基础上构建的便捷函数。如果应用程序需要对属性更改行为的配置进行更多控制,则可以使用此系统,如 AttributeEvents
中所述。
对象名称 | 描述 |
---|---|
validates(*names, [include_removes, include_backrefs]) |
将一个方法装饰为一个或多个命名属性的“验证器”。 |
- function sqlalchemy.orm.validates(*names: str, include_removes: bool = False, include_backrefs: bool = True) → Callable[[_Fn], _Fn]¶
将一个方法装饰为一个或多个命名属性的“验证器”。
将一个方法指定为验证器,它接收属性的名称以及要分配的值,或者在集合的情况下,接收要添加到集合的值。然后,该函数可以引发验证异常以停止该过程的继续(其中 Python 的内置
ValueError
和AssertionError
异常是合理的选项),也可以在继续之前修改或替换该值。否则,该函数应返回给定值。请注意,针对集合的验证器不能在验证例程中发出该集合的加载 - 此用法会引发断言以避免递归溢出。这是一种不支持的重新进入条件。
- 参数:
*names¶ – 要验证的属性名称列表。
include_removes¶ – 如果为 True,则还会发送“移除”事件 - 验证函数必须接受一个额外的参数“is_remove”,它将是一个布尔值。
include_backrefs¶ –
默认值为
True
;如果为False
,则如果事件的来源是通过反向引用关联的属性事件,则验证函数不会发出。当使用双向validates()
时,可以使用此选项确保每次属性操作只发出一个验证器。在版本 2.0.16 中更改: 此参数在 2.0.0 到 2.0.15 版本中不慎默认为
False
。在 2.0.16 版本中恢复了其正确的默认值True
。
另请参阅
简单验证器 -
validates()
的使用示例
在核心级别使用自定义数据类型¶
一种非 ORM 方式,可以通过对映射的 Table
元数据应用自定义数据类型来影响列的值,以适合在 Python 中表示的数据与在数据库中表示的数据之间的转换。这在数据进入数据库和返回时都发生某种类型的编码/解码的情况下更常见;有关此方面的更多信息,请参阅核心文档中的 增强现有类型。
使用描述符和混合属性¶
一个更全面的方法来修改属性的行为是使用描述符。在 Python 中,通常使用 property()
函数。SQLAlchemy 中的标准描述符技术是创建一个简单的描述符,并让它从一个映射属性中读取/写入,但使用不同的名称。下面我们将使用 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
对象会将值通过 email
描述符传递到 _email
映射属性,但类级 EmailAddress.email
属性不具有与 Select
可用的标准表达式语义。为了提供这些语义,我们可以使用 hybrid
扩展,如下所示:
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 == "[email protected]")
).one()
SELECT address.email AS address_email, address.id AS address_id
FROM address
WHERE address.email = ?
('[email protected]',)
address.email = "[email protected]"
session.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
。当我们针对 email
属性进行查询时,会渲染一个 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')
有关混合属性的更多信息,请参阅 混合属性。
同义词¶
同义词是一种映射器级别的构造,它允许类中的任何属性“镜像”另一个被映射的属性。
从最基本的意义上讲,同义词是一种简单的方法,可以让某个属性可以使用另一个名称。
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
的子类,包括映射的列和关系,以及同义词本身。
除了简单的镜像,synonym()
还可以用来引用用户定义的 描述符。我们可以为 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)
在使用声明式时,可以使用 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()
对于简单的镜像非常有用,但使用描述符来增强属性行为的用例,在现代使用中最好使用 混合属性 功能来处理,该功能更侧重于 Python 描述符。从技术上讲,synonym()
可以完成 hybrid_property
可以完成的所有事情,因为它也支持注入自定义 SQL 功能,但混合属性在更复杂的情况下更容易使用。
对象名称 | 描述 |
---|---|
synonym(name, *, [map_column, descriptor, comparator_factory, init, repr, default, default_factory, compare, kw_only, hash, info, doc]) |
将一个属性名指定为映射属性的同义词,这样该属性就会镜像另一个属性的值和表达式行为。 |
- function sqlalchemy.orm.synonym(name: str, *, map_column: bool | None = None, descriptor: Any | None = None, comparator_factory: Type[PropComparator[_T]] | None = None, init: _NoArg | bool = _NoArg.NO_ARG, repr: _NoArg | bool = _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG, default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG, compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg | bool = _NoArg.NO_ARG, hash: _NoArg | bool | None = _NoArg.NO_ARG, info: _InfoType | None = None, doc: str | None = None) → Synonym[Any]¶
将一个属性名指定为映射属性的同义词,这样该属性就会镜像另一个属性的值和表达式行为。
e.g.
class MyClass(Base): __tablename__ = 'my_table' id = Column(Integer, primary_key=True) job_status = Column(String(50)) status = synonym("job_status")
- 参数:
name¶ – 已映射属性的名称。它可以指代在类上配置的 ORM 映射属性的字符串名称,包括列绑定属性和关系。
descriptor¶ – 当在实例级别访问此属性时,将用作 getter(以及可能的 setter)的 Python 描述符。
map_column¶ –
仅适用于经典映射和针对现有 Table 对象的映射。如果为
True
,则synonym()
构造将定位映射表上通常与该同义词的属性名称相关的Column
对象,并生成一个新的ColumnProperty
,该属性将该Column
映射到同义词的“name”参数中给出的替代名称;这样,通常无需重新定义Column
的映射,使其位于不同的名称下。这通常用于将Column
替换为也使用描述符的属性,即与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
使用 Declarative 时,为了提供与同义词结合使用的描述符,请使用
sqlalchemy.ext.declarative.synonym_for()
助手。但是,请注意,混合属性 功能通常应该优先考虑,特别是在重新定义属性行为时。info¶ – 可选的数据字典,它将填充到此对象的
InspectionAttr.info
属性中。comparator_factory¶ –
一个
PropComparator
的子类,它将在 SQL 表达式级别提供自定义比较行为。注意
对于提供重新定义属性的 Python 级别和 SQL 表达式级别行为的属性的用例,请参阅 使用描述符和混合 中介绍的混合属性,以获得比同义词更有效的技术。
运算符自定义¶
SQLAlchemy ORM 和 Core 表达式语言使用的“运算符”是完全可自定义的。例如,比较表达式 User.name == 'ed'
使用了内置于 Python 本身的运算符 operator.eq
- SQLAlchemy 与该运算符关联的实际 SQL 结构可以修改。新的运算符也可以与列表达式关联。列表达式发生的运算符最直接地在类型级别重新定义 - 请参阅部分 重新定义和创建新的运算符 以获取描述。
像 column_property()
、relationship()
和 composite()
这样的 ORM 级别函数也通过将 PropComparator
子类传递给每个函数的 comparator_factory
参数来提供在 ORM 级别重新定义运算符的功能。在该级别自定义运算符是一个很少见的使用案例。有关概述,请参阅 PropComparator
的文档。
flambé! 火龙和 炼金术士 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:2024 年 11 月 8 日星期五上午 8:41:19 EST