更改属性行为

优质
小牛编辑
133浏览
2023-12-01

简单验证器

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

from sqlalchemy.orm import validates

class EmailAddress(Base):
    __tablename__ = 'address'

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

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

在 1.0.0 版更改: -当获取新获取的主键列值以及一些Python或服务器端默认值时,在刷新过程中不再触发验证器。在1.0之前,在这些情况下也可能触发验证器。

当项添加到集合时,验证程序还接收集合附加事件:

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

如果相互依赖的验证器通过backref链接,也可以使用 include_backrefs=False 选项;此选项,当设置为 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

上面,如果我们要分配给 Address.user 如在 some_address.user = some_user , the validate_address() 函数将 not 即使发生附加 some_user.addresses -事件是由backref引起的。

请注意 validates() decorator是一个建立在属性事件之上的方便函数。需要更多控制属性更改行为配置的应用程序可以使用此系统,如中所述 AttributeEvents .

Object NameDescription

validates(*names, **kw)

将方法修饰为一个或多个命名属性的“验证器”。

function sqlalchemy.orm.validates(*names, **kw)

通过使用应用于映射的自定义数据类型,可以实现以适合在Python中表示数据与在数据库中表示数据之间转换数据的方式影响列值的非ORM方法 Table 元数据。这种情况在某些编码/解码样式的情况下更为常见,这种编码/解码在数据进入数据库和返回数据时都会发生;请在 扩充现有类型 .

使用描述符和混合

为属性生成修改行为的更全面的方法是使用 descriptors . 这些通常在使用 property() 功能。描述符的标准SQLAlchemy技术是创建一个普通的描述符,并让它从具有不同名称的映射属性中读/写。下面我们使用python 2.6样式的属性说明这一点:

class EmailAddress(Base):
    __tablename__ = 'email_address'

    id = Column(Integer, primary_key=True)

    # name the attribute with an underscore,
    # different from the column name
    _email = 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 属性没有可用于的常规表达式语义 Query . 为了提供这些,我们使用 hybrid 扩展如下:

from sqlalchemy.ext.hybrid import hybrid_property

class EmailAddress(Base):
    __tablename__ = 'email_address'

    id = Column(Integer, primary_key=True)

    _email = Column("email", String)

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

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

这个 .email 属性,除了当我们有一个 EmailAddress ,还提供在类级别使用时的SQL表达式,即从 EmailAddress 直接上课:

from sqlalchemy.orm import Session
session = Session()

sqladdress = session.query(EmailAddress).\
                 filter(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'
sqlsession.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 = Column(Integer, primary_key=True)

    _email = 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)

上面,访问 email 的实例的属性 EmailAddress 将返回 _email 属性,删除或添加主机名 @example.com 从值开始。当我们对 email 属性,将呈现产生相同效果的SQL函数:

sqladdress = session.query(EmailAddress).filter(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 = Column(Integer, primary_key=True)
    job_status = 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() 也可以引用用户定义的 descriptor . 我们可以提供 status 同义词 @property ::

class MyClass(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)
    status = 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 = Column(Integer, primary_key=True)
    status = Column(String(50))

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

synonym() 对于简单的镜像很有用,在现代使用中,使用 hybrid attribute 特性,它更倾向于Python描述符。技术上,A synonym() 能做的每件事 hybrid_property 可以,因为它还支持自定义SQL功能的注入,但是在更复杂的情况下,混合更容易使用。

Object NameDescription

synonym(name[, map_column, descriptor, comparator_factory, ...])

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

function sqlalchemy.orm.synonym(name, map_column=None, descriptor=None, comparator_factory=None, doc=None, info=None)

SQLAlchemy ORM和核心表达式语言使用的“操作符”是完全可自定义的。例如,比较表达式 User.name == 'ed' 使用内置于python本身的运算符 operator.eq -可以修改sqlAlchemy与此类运算符关联的实际sql构造。新操作也可以与列表达式关联。列表达式所用的运算符在类型级别上最直接地重新定义-请参见部分 重新定义和创建新的运算符 以获取描述。

ORM级功能 column_property()relationship()composite() 还通过传递 PropComparator 子类到 comparator_factory 每个函数的参数。这一级别的操作员定制是一个罕见的用例。参见文档 PropComparator 以获取概述。