Changing Attribute Behavior

    A quick way to add a “validation” routine to an attribute is to use the decorator. An attribute validator can raise an exception, halting the process of mutating the attribute’s value, or can change the given value into something different. Validators, like all attribute extensions, are only called by normal userland code; they are not issued when the ORM is populating the object:

    Validators also receive collection append events, when items are added to a collection:

    1. class User(Base):
    2. # ...
    3. addresses = relationship("Address")
    4. @validates("addresses")
    5. def validate_address(self, key, address):
    6. if "@" not in address.email:
    7. raise ValueError("failed simplified email validation")
    8. return address

    The validation function by default does not get emitted for collection remove events, as the typical expectation is that a value being discarded doesn’t require validation. However, validates() supports reception of these events by specifying include_removes=True to the decorator. When this flag is set, the validation function must receive an additional boolean argument which if True indicates that the operation is a removal:

    1. from sqlalchemy.orm import validates
    2. class User(Base):
    3. # ...
    4. addresses = relationship("Address")
    5. @validates("addresses", include_removes=True)
    6. def validate_address(self, key, address, is_remove):
    7. if is_remove:
    8. raise ValueError("not allowed to remove items from the collection")
    9. else:
    10. if "@" not in address.email:
    11. raise ValueError("failed simplified email validation")
    12. return address

    The case where mutually dependent validators are linked via a backref can also be tailored, using the include_backrefs=False option; this option, when set to False, prevents a validation function from emitting if the event occurs as a result of a backref:

    1. from sqlalchemy.orm import validates
    2. class User(Base):
    3. # ...
    4. addresses = relationship("Address", backref="user")
    5. @validates("addresses", include_backrefs=False)
    6. def validate_address(self, key, address):
    7. if "@" not in address:
    8. raise ValueError("failed simplified email validation")
    9. return address

    Above, if we were to assign to Address.user as in some_address.user = some_user, the validate_address() function would not be emitted, even though an append occurs to some_user.addresses - the event is caused by a backref.

    Note that the decorator is a convenience function built on top of attribute events. An application that requires more control over configuration of attribute change behavior can make use of this system, described at AttributeEvents.

    function sqlalchemy.orm.validates(*names: str, include_removes: bool = False, include_backrefs: bool = False) → Callable[[_Fn], _Fn]

    Decorate a method as a ‘validator’ for one or more named properties.

    Designates a method as a validator, a method which receives the name of the attribute as well as a value to be assigned, or in the case of a collection, the value to be added to the collection. The function can then raise validation exceptions to halt the process from continuing (where Python’s built-in ValueError and AssertionError exceptions are reasonable choices), or can modify or replace the value before proceeding. The function should otherwise return the given value.

    Note that a validator for a collection cannot issue a load of that collection within the validation routine - this usage raises an assertion to avoid recursion overflows. This is a reentrant condition which is not supported.

    • Parameters:

      • *names – list of attribute names to be validated.

      • include_removes – if True, “remove” events will be sent as well - the validation function must accept an additional argument “is_remove” which will be a boolean.

      • include_backrefs

        defaults to True; if False, the validation function will not emit if the originator is an attribute event related via a backref. This can be used for bi-directional usage where only one validator should emit per attribute operation.

    See also

    Simple Validators - usage examples for

    A non-ORM means of affecting the value of a column in a way that suits converting data between how it is represented in Python, vs. how it is represented in the database, can be achieved by using a custom datatype that is applied to the mapped metadata. This is more common in the case of some style of encoding / decoding that occurs both as data goes to the database and as it is returned; read more about this in the Core documentation at Augmenting Existing Types.

    A more comprehensive way to produce modified behavior for an attribute is to use descriptors. These are commonly used in Python using the property() function. The standard SQLAlchemy technique for descriptors is to create a plain descriptor, and to have it read/write from a mapped attribute with a different name. Below we illustrate this using Python 2.6-style properties:

    1. class EmailAddress(Base):
    2. __tablename__ = "email_address"
    3. id = mapped_column(Integer, primary_key=True)
    4. # name the attribute with an underscore,
    5. # different from the column name
    6. _email = mapped_column("email", String)
    7. # then create an ".email" attribute
    8. # to get/set "._email"
    9. @property
    10. def email(self):
    11. return self._email
    12. @email.setter
    13. self._email = email

    The approach above will work, but there’s more we can add. While our EmailAddress object will shuttle the value through the email descriptor and into the mapped attribute, the class level EmailAddress.email attribute does not have the usual expression semantics usable with . To provide these, we instead use the hybrid extension as follows:

    1. from sqlalchemy.ext.hybrid import hybrid_property
    2. class EmailAddress(Base):
    3. __tablename__ = "email_address"
    4. id = mapped_column(Integer, primary_key=True)
    5. _email = mapped_column("email", String)
    6. @hybrid_property
    7. def email(self):
    8. return self._email
    9. @email.setter
    10. def email(self, email):
    11. self._email = email

    The .email attribute, in addition to providing getter/setter behavior when we have an instance of EmailAddress, also provides a SQL expression when used at the class level, that is, from the EmailAddress class directly:

    The also allows us to change the behavior of the attribute, including defining separate behaviors when the attribute is accessed at the instance level versus at the class/expression level, using the hybrid_property.expression() modifier. Such as, if we wanted to add a host name automatically, we might define two sets of string manipulation logic:

    1. class EmailAddress(Base):
    2. __tablename__ = "email_address"
    3. id = mapped_column(Integer, primary_key=True)
    4. _email = mapped_column("email", String)
    5. @hybrid_property
    6. def email(self):
    7. """Return the value of _email up until the last twelve
    8. characters."""
    9. return self._email[:-12]
    10. @email.setter
    11. def email(self, email):
    12. """Set the value of _email, tacking on the twelve character
    13. value @example.com."""
    14. self._email = email + "@example.com"
    15. @email.expression
    16. def email(cls):
    17. """Produce a SQL expression that represents the value
    18. of the _email column, minus the last twelve characters."""
    19. return func.substr(cls._email, 0, func.length(cls._email) - 12)

    Above, accessing the email property of an instance of EmailAddress will return the value of the _email attribute, removing or adding the hostname @example.com from the value. When we query against the email attribute, a SQL function is rendered which produces the same effect:

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

    Read more about Hybrids at .

    Synonyms are a mapper-level construct that allow any attribute on a class to “mirror” another attribute that is mapped.

    In the most basic sense, the synonym is an easy way to make a certain attribute available by an additional name:

    1. from sqlalchemy.orm import synonym
    2. class MyClass(Base):
    3. __tablename__ = "my_table"
    4. id = mapped_column(Integer, primary_key=True)
    5. job_status = mapped_column(String(50))
    6. status = synonym("job_status")

    The above class MyClass has two attributes, .job_status and .status that will behave as one attribute, both at the expression level:

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

    and at the instance level:

    1. >>> m1 = MyClass(status="x")
    2. ('x', 'x')
    3. >>> m1.job_status = "y"
    4. >>> m1.status, m1.job_status
    5. ('y', 'y')

    The can be used for any kind of mapped attribute that subclasses MapperProperty, including mapped columns and relationships, as well as synonyms themselves.

    Beyond a simple mirror, can also be made to reference a user-defined descriptor. We can supply our status synonym with a @property:

    When using Declarative, the above pattern can be expressed more succinctly using the decorator:

    1. from sqlalchemy.ext.declarative import synonym_for
    2. class MyClass(Base):
    3. __tablename__ = "my_table"
    4. id = mapped_column(Integer, primary_key=True)
    5. status = mapped_column(String(50))
    6. @synonym_for("status")
    7. @property
    8. def job_status(self):
    9. return "Status: " + self.status

    While the synonym() is useful for simple mirroring, the use case of augmenting attribute behavior with descriptors is better handled in modern usage using the feature, which is more oriented towards Python descriptors. Technically, a synonym() can do everything that a can do, as it also supports injection of custom SQL capabilities, but the hybrid is more straightforward to use in more complex situations.

    Object NameDescription

    Denote an attribute name as a synonym to a mapped property, in that the attribute will mirror the value and expression behavior of another attribute.

    function sqlalchemy.orm.synonym(name: str, *, map_column: Optional[bool] = None, descriptor: Optional[Any] = None, comparator_factory: Optional[Type[PropComparator[_T]]] = None, init: Union[_NoArg, bool] = _NoArg.NO_ARG, repr: Union[_NoArg, bool] = _NoArg.NO_ARG, default: Union[_NoArg, _T] = _NoArg.NO_ARG, default_factory: Union[_NoArg, Callable[[], _T]] = _NoArg.NO_ARG, compare: Union[_NoArg, bool] = _NoArg.NO_ARG, kw_only: Union[_NoArg, bool] = _NoArg.NO_ARG, info: Optional[_InfoType] = None, doc: Optional[str] = None) → [Any]

    Denote an attribute name as a synonym to a mapped property, in that the attribute will mirror the value and expression behavior of another attribute.

    e.g.:

    1. class MyClass(Base):
    2. __tablename__ = 'my_table'
    3. id = Column(Integer, primary_key=True)
    4. job_status = Column(String(50))
    5. status = synonym("job_status")
    • Parameters:

      • name – the name of the existing mapped property. This can refer to the string name ORM-mapped attribute configured on the class, including column-bound attributes and relationships.

      • descriptor – a Python descriptor that will be used as a getter (and potentially a setter) when this attribute is accessed at the instance level.

      • map_column

        For classical mappings and mappings against an existing Table object only. if True, the construct will locate the Column object upon the mapped table that would normally be associated with the attribute name of this synonym, and produce a new that instead maps this Column to the alternate name given as the “name” argument of the synonym; in this way, the usual step of redefining the mapping of the to be under a different name is unnecessary. This is usually intended to be used when a Column is to be replaced with an attribute that also uses a descriptor, that is, in conjunction with the parameter:

        ``` my_table = Table(

        1. "my_table", metadata,
        2. Column('id', Integer, primary_key=True),
        3. Column('job_status', String(50))

        )

        class MyClass:

        1. @property
        2. def _job_status_descriptor(self):
        3. return "Status: %s" % self._job_status
    1. mapper(
    2. MyClass, my_table, properties={
    3. "job_status": synonym(
    4. "_job_status", map_column=True,
    5. descriptor=MyClass._job_status_descriptor)
    6. }
    7. )
    8. ```
    9. Above, the attribute named `_job_status` is automatically mapped to the `job_status` column:
    10. ```
    11. >>> j1 = MyClass()
    12. >>> j1._job_status = "employed"
    13. >>> j1.job_status
    14. Status: employed
    15. ```
    16. When using Declarative, in order to provide a descriptor in conjunction with a synonym, use the `sqlalchemy.ext.declarative.synonym_for()` helper. However, note that the [hybrid properties](#mapper-hybrids) feature should usually be preferred, particularly when redefining attribute behavior.
    17. - **info** –
    18. Optional data dictionary which will be populated into the `InspectionAttr.info` attribute of this object.
    19. New in version 1.0.0.
    20. - **comparator\_factory** –
    21. A subclass of [PropComparator]($376e1901d3af4d61.md#sqlalchemy.orm.PropComparator "sqlalchemy.orm.PropComparator") that will provide custom comparison behavior at the SQL expression level.
    22. Note

    See also

    Synonyms - Overview of synonyms

    - a helper oriented towards Declarative

    Using Descriptors and Hybrids - The Hybrid Attribute extension provides an updated approach to augmenting attribute behavior more flexibly than can be achieved with synonyms.

    The “operators” used by the SQLAlchemy ORM and Core expression language are fully customizable. For example, the comparison expression User.name == 'ed' makes usage of an operator built into Python itself called operator.eq - the actual SQL construct which SQLAlchemy associates with such an operator can be modified. New operations can be associated with column expressions as well. The operators which take place for column expressions are most directly redefined at the type level - see the section Redefining and Creating New Operators for a description.