You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1201 lines
						
					
					
						
							41 KiB
						
					
					
				
			
		
		
	
	
							1201 lines
						
					
					
						
							41 KiB
						
					
					
				# ext/hybrid.py
 | 
						|
# Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
 | 
						|
# <see AUTHORS file>
 | 
						|
#
 | 
						|
# This module is part of SQLAlchemy and is released under
 | 
						|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
 | 
						|
 | 
						|
r"""Define attributes on ORM-mapped classes that have "hybrid" behavior.
 | 
						|
 | 
						|
"hybrid" means the attribute has distinct behaviors defined at the
 | 
						|
class level and at the instance level.
 | 
						|
 | 
						|
The :mod:`~sqlalchemy.ext.hybrid` extension provides a special form of
 | 
						|
method decorator, is around 50 lines of code and has almost no
 | 
						|
dependencies on the rest of SQLAlchemy.  It can, in theory, work with
 | 
						|
any descriptor-based expression system.
 | 
						|
 | 
						|
Consider a mapping ``Interval``, representing integer ``start`` and ``end``
 | 
						|
values. We can define higher level functions on mapped classes that produce SQL
 | 
						|
expressions at the class level, and Python expression evaluation at the
 | 
						|
instance level.  Below, each function decorated with :class:`.hybrid_method` or
 | 
						|
:class:`.hybrid_property` may receive ``self`` as an instance of the class, or
 | 
						|
as the class itself::
 | 
						|
 | 
						|
    from sqlalchemy import Column, Integer
 | 
						|
    from sqlalchemy.ext.declarative import declarative_base
 | 
						|
    from sqlalchemy.orm import Session, aliased
 | 
						|
    from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
 | 
						|
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class Interval(Base):
 | 
						|
        __tablename__ = 'interval'
 | 
						|
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        start = Column(Integer, nullable=False)
 | 
						|
        end = Column(Integer, nullable=False)
 | 
						|
 | 
						|
        def __init__(self, start, end):
 | 
						|
            self.start = start
 | 
						|
            self.end = end
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def length(self):
 | 
						|
            return self.end - self.start
 | 
						|
 | 
						|
        @hybrid_method
 | 
						|
        def contains(self, point):
 | 
						|
            return (self.start <= point) & (point <= self.end)
 | 
						|
 | 
						|
        @hybrid_method
 | 
						|
        def intersects(self, other):
 | 
						|
            return self.contains(other.start) | self.contains(other.end)
 | 
						|
 | 
						|
Above, the ``length`` property returns the difference between the
 | 
						|
``end`` and ``start`` attributes.  With an instance of ``Interval``,
 | 
						|
this subtraction occurs in Python, using normal Python descriptor
 | 
						|
mechanics::
 | 
						|
 | 
						|
    >>> i1 = Interval(5, 10)
 | 
						|
    >>> i1.length
 | 
						|
    5
 | 
						|
 | 
						|
When dealing with the ``Interval`` class itself, the :class:`.hybrid_property`
 | 
						|
descriptor evaluates the function body given the ``Interval`` class as
 | 
						|
the argument, which when evaluated with SQLAlchemy expression mechanics
 | 
						|
(here using the :attr:`.QueryableAttribute.expression` accessor)
 | 
						|
returns a new SQL expression::
 | 
						|
 | 
						|
    >>> print(Interval.length.expression)
 | 
						|
    interval."end" - interval.start
 | 
						|
 | 
						|
    >>> print(Session().query(Interval).filter(Interval.length > 10))
 | 
						|
    SELECT interval.id AS interval_id, interval.start AS interval_start,
 | 
						|
    interval."end" AS interval_end
 | 
						|
    FROM interval
 | 
						|
    WHERE interval."end" - interval.start > :param_1
 | 
						|
 | 
						|
ORM methods such as :meth:`_query.Query.filter_by`
 | 
						|
generally use ``getattr()`` to
 | 
						|
locate attributes, so can also be used with hybrid attributes::
 | 
						|
 | 
						|
    >>> print(Session().query(Interval).filter_by(length=5))
 | 
						|
    SELECT interval.id AS interval_id, interval.start AS interval_start,
 | 
						|
    interval."end" AS interval_end
 | 
						|
    FROM interval
 | 
						|
    WHERE interval."end" - interval.start = :param_1
 | 
						|
 | 
						|
The ``Interval`` class example also illustrates two methods,
 | 
						|
``contains()`` and ``intersects()``, decorated with
 | 
						|
:class:`.hybrid_method`. This decorator applies the same idea to
 | 
						|
methods that :class:`.hybrid_property` applies to attributes.   The
 | 
						|
methods return boolean values, and take advantage of the Python ``|``
 | 
						|
and ``&`` bitwise operators to produce equivalent instance-level and
 | 
						|
SQL expression-level boolean behavior::
 | 
						|
 | 
						|
    >>> i1.contains(6)
 | 
						|
    True
 | 
						|
    >>> i1.contains(15)
 | 
						|
    False
 | 
						|
    >>> i1.intersects(Interval(7, 18))
 | 
						|
    True
 | 
						|
    >>> i1.intersects(Interval(25, 29))
 | 
						|
    False
 | 
						|
 | 
						|
    >>> print(Session().query(Interval).filter(Interval.contains(15)))
 | 
						|
    SELECT interval.id AS interval_id, interval.start AS interval_start,
 | 
						|
    interval."end" AS interval_end
 | 
						|
    FROM interval
 | 
						|
    WHERE interval.start <= :start_1 AND interval."end" > :end_1
 | 
						|
 | 
						|
    >>> ia = aliased(Interval)
 | 
						|
    >>> print(Session().query(Interval, ia).filter(Interval.intersects(ia)))
 | 
						|
    SELECT interval.id AS interval_id, interval.start AS interval_start,
 | 
						|
    interval."end" AS interval_end, interval_1.id AS interval_1_id,
 | 
						|
    interval_1.start AS interval_1_start, interval_1."end" AS interval_1_end
 | 
						|
    FROM interval, interval AS interval_1
 | 
						|
    WHERE interval.start <= interval_1.start
 | 
						|
        AND interval."end" > interval_1.start
 | 
						|
        OR interval.start <= interval_1."end"
 | 
						|
        AND interval."end" > interval_1."end"
 | 
						|
 | 
						|
.. _hybrid_distinct_expression:
 | 
						|
 | 
						|
Defining Expression Behavior Distinct from Attribute Behavior
 | 
						|
--------------------------------------------------------------
 | 
						|
 | 
						|
Our usage of the ``&`` and ``|`` bitwise operators above was
 | 
						|
fortunate, considering our functions operated on two boolean values to
 | 
						|
return a new one.   In many cases, the construction of an in-Python
 | 
						|
function and a SQLAlchemy SQL expression have enough differences that
 | 
						|
two separate Python expressions should be defined.  The
 | 
						|
:mod:`~sqlalchemy.ext.hybrid` decorators define the
 | 
						|
:meth:`.hybrid_property.expression` modifier for this purpose.   As an
 | 
						|
example we'll define the radius of the interval, which requires the
 | 
						|
usage of the absolute value function::
 | 
						|
 | 
						|
    from sqlalchemy import func
 | 
						|
 | 
						|
    class Interval(object):
 | 
						|
        # ...
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def radius(self):
 | 
						|
            return abs(self.length) / 2
 | 
						|
 | 
						|
        @radius.expression
 | 
						|
        def radius(cls):
 | 
						|
            return func.abs(cls.length) / 2
 | 
						|
 | 
						|
Above the Python function ``abs()`` is used for instance-level
 | 
						|
operations, the SQL function ``ABS()`` is used via the :data:`.func`
 | 
						|
object for class-level expressions::
 | 
						|
 | 
						|
    >>> i1.radius
 | 
						|
    2
 | 
						|
 | 
						|
    >>> print(Session().query(Interval).filter(Interval.radius > 5))
 | 
						|
    SELECT interval.id AS interval_id, interval.start AS interval_start,
 | 
						|
        interval."end" AS interval_end
 | 
						|
    FROM interval
 | 
						|
    WHERE abs(interval."end" - interval.start) / :abs_1 > :param_1
 | 
						|
 | 
						|
.. note:: When defining an expression for a hybrid property or method, the
 | 
						|
   expression method **must** retain the name of the original hybrid, else
 | 
						|
   the new hybrid with the additional state will be attached to the class
 | 
						|
   with the non-matching name. To use the example above::
 | 
						|
 | 
						|
    class Interval(object):
 | 
						|
        # ...
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def radius(self):
 | 
						|
            return abs(self.length) / 2
 | 
						|
 | 
						|
        # WRONG - the non-matching name will cause this function to be
 | 
						|
        # ignored
 | 
						|
        @radius.expression
 | 
						|
        def radius_expression(cls):
 | 
						|
            return func.abs(cls.length) / 2
 | 
						|
 | 
						|
   This is also true for other mutator methods, such as
 | 
						|
   :meth:`.hybrid_property.update_expression`. This is the same behavior
 | 
						|
   as that of the ``@property`` construct that is part of standard Python.
 | 
						|
 | 
						|
Defining Setters
 | 
						|
----------------
 | 
						|
 | 
						|
Hybrid properties can also define setter methods.  If we wanted
 | 
						|
``length`` above, when set, to modify the endpoint value::
 | 
						|
 | 
						|
    class Interval(object):
 | 
						|
        # ...
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def length(self):
 | 
						|
            return self.end - self.start
 | 
						|
 | 
						|
        @length.setter
 | 
						|
        def length(self, value):
 | 
						|
            self.end = self.start + value
 | 
						|
 | 
						|
The ``length(self, value)`` method is now called upon set::
 | 
						|
 | 
						|
    >>> i1 = Interval(5, 10)
 | 
						|
    >>> i1.length
 | 
						|
    5
 | 
						|
    >>> i1.length = 12
 | 
						|
    >>> i1.end
 | 
						|
    17
 | 
						|
 | 
						|
.. _hybrid_bulk_update:
 | 
						|
 | 
						|
Allowing Bulk ORM Update
 | 
						|
------------------------
 | 
						|
 | 
						|
A hybrid can define a custom "UPDATE" handler for when using the
 | 
						|
:meth:`_query.Query.update` method, allowing the hybrid to be used in the
 | 
						|
SET clause of the update.
 | 
						|
 | 
						|
Normally, when using a hybrid with :meth:`_query.Query.update`, the SQL
 | 
						|
expression is used as the column that's the target of the SET.  If our
 | 
						|
``Interval`` class had a hybrid ``start_point`` that linked to
 | 
						|
``Interval.start``, this could be substituted directly::
 | 
						|
 | 
						|
    session.query(Interval).update({Interval.start_point: 10})
 | 
						|
 | 
						|
However, when using a composite hybrid like ``Interval.length``, this
 | 
						|
hybrid represents more than one column.   We can set up a handler that will
 | 
						|
accommodate a value passed to :meth:`_query.Query.update` which can affect
 | 
						|
this, using the :meth:`.hybrid_property.update_expression` decorator.
 | 
						|
A handler that works similarly to our setter would be::
 | 
						|
 | 
						|
    class Interval(object):
 | 
						|
        # ...
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def length(self):
 | 
						|
            return self.end - self.start
 | 
						|
 | 
						|
        @length.setter
 | 
						|
        def length(self, value):
 | 
						|
            self.end = self.start + value
 | 
						|
 | 
						|
        @length.update_expression
 | 
						|
        def length(cls, value):
 | 
						|
            return [
 | 
						|
                (cls.end, cls.start + value)
 | 
						|
            ]
 | 
						|
 | 
						|
Above, if we use ``Interval.length`` in an UPDATE expression as::
 | 
						|
 | 
						|
    session.query(Interval).update(
 | 
						|
        {Interval.length: 25}, synchronize_session='fetch')
 | 
						|
 | 
						|
We'll get an UPDATE statement along the lines of::
 | 
						|
 | 
						|
    UPDATE interval SET end=start + :value
 | 
						|
 | 
						|
In some cases, the default "evaluate" strategy can't perform the SET
 | 
						|
expression in Python; while the addition operator we're using above
 | 
						|
is supported, for more complex SET expressions it will usually be necessary
 | 
						|
to use either the "fetch" or False synchronization strategy as illustrated
 | 
						|
above.
 | 
						|
 | 
						|
.. note:: For ORM bulk updates to work with hybrids, the function name
 | 
						|
   of the hybrid must match that of how it is accessed.    Something
 | 
						|
   like this wouldn't work::
 | 
						|
 | 
						|
        class Interval(object):
 | 
						|
            # ...
 | 
						|
 | 
						|
            def _get(self):
 | 
						|
                return self.end - self.start
 | 
						|
 | 
						|
            def _set(self, value):
 | 
						|
                self.end = self.start + value
 | 
						|
 | 
						|
            def _update_expr(cls, value):
 | 
						|
                return [
 | 
						|
                    (cls.end, cls.start + value)
 | 
						|
                ]
 | 
						|
 | 
						|
            length = hybrid_property(
 | 
						|
                fget=_get, fset=_set, update_expr=_update_expr
 | 
						|
            )
 | 
						|
 | 
						|
    The Python descriptor protocol does not provide any reliable way for
 | 
						|
    a descriptor to know what attribute name it was accessed as, and
 | 
						|
    the UPDATE scheme currently relies upon being able to access the
 | 
						|
    attribute from an instance by name in order to perform the instance
 | 
						|
    synchronization step.
 | 
						|
 | 
						|
.. versionadded:: 1.2 added support for bulk updates to hybrid properties.
 | 
						|
 | 
						|
Working with Relationships
 | 
						|
--------------------------
 | 
						|
 | 
						|
There's no essential difference when creating hybrids that work with
 | 
						|
related objects as opposed to column-based data. The need for distinct
 | 
						|
expressions tends to be greater.  The two variants we'll illustrate
 | 
						|
are the "join-dependent" hybrid, and the "correlated subquery" hybrid.
 | 
						|
 | 
						|
Join-Dependent Relationship Hybrid
 | 
						|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
						|
 | 
						|
Consider the following declarative
 | 
						|
mapping which relates a ``User`` to a ``SavingsAccount``::
 | 
						|
 | 
						|
    from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
 | 
						|
    from sqlalchemy.orm import relationship
 | 
						|
    from sqlalchemy.ext.declarative import declarative_base
 | 
						|
    from sqlalchemy.ext.hybrid import hybrid_property
 | 
						|
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class SavingsAccount(Base):
 | 
						|
        __tablename__ = 'account'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
 | 
						|
        balance = Column(Numeric(15, 5))
 | 
						|
 | 
						|
    class User(Base):
 | 
						|
        __tablename__ = 'user'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        name = Column(String(100), nullable=False)
 | 
						|
 | 
						|
        accounts = relationship("SavingsAccount", backref="owner")
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def balance(self):
 | 
						|
            if self.accounts:
 | 
						|
                return self.accounts[0].balance
 | 
						|
            else:
 | 
						|
                return None
 | 
						|
 | 
						|
        @balance.setter
 | 
						|
        def balance(self, value):
 | 
						|
            if not self.accounts:
 | 
						|
                account = Account(owner=self)
 | 
						|
            else:
 | 
						|
                account = self.accounts[0]
 | 
						|
            account.balance = value
 | 
						|
 | 
						|
        @balance.expression
 | 
						|
        def balance(cls):
 | 
						|
            return SavingsAccount.balance
 | 
						|
 | 
						|
The above hybrid property ``balance`` works with the first
 | 
						|
``SavingsAccount`` entry in the list of accounts for this user.   The
 | 
						|
in-Python getter/setter methods can treat ``accounts`` as a Python
 | 
						|
list available on ``self``.
 | 
						|
 | 
						|
However, at the expression level, it's expected that the ``User`` class will
 | 
						|
be used in an appropriate context such that an appropriate join to
 | 
						|
``SavingsAccount`` will be present::
 | 
						|
 | 
						|
    >>> print(Session().query(User, User.balance).
 | 
						|
    ...       join(User.accounts).filter(User.balance > 5000))
 | 
						|
    SELECT "user".id AS user_id, "user".name AS user_name,
 | 
						|
    account.balance AS account_balance
 | 
						|
    FROM "user" JOIN account ON "user".id = account.user_id
 | 
						|
    WHERE account.balance > :balance_1
 | 
						|
 | 
						|
Note however, that while the instance level accessors need to worry
 | 
						|
about whether ``self.accounts`` is even present, this issue expresses
 | 
						|
itself differently at the SQL expression level, where we basically
 | 
						|
would use an outer join::
 | 
						|
 | 
						|
    >>> from sqlalchemy import or_
 | 
						|
    >>> print (Session().query(User, User.balance).outerjoin(User.accounts).
 | 
						|
    ...         filter(or_(User.balance < 5000, User.balance == None)))
 | 
						|
    SELECT "user".id AS user_id, "user".name AS user_name,
 | 
						|
    account.balance AS account_balance
 | 
						|
    FROM "user" LEFT OUTER JOIN account ON "user".id = account.user_id
 | 
						|
    WHERE account.balance <  :balance_1 OR account.balance IS NULL
 | 
						|
 | 
						|
Correlated Subquery Relationship Hybrid
 | 
						|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
						|
 | 
						|
We can, of course, forego being dependent on the enclosing query's usage
 | 
						|
of joins in favor of the correlated subquery, which can portably be packed
 | 
						|
into a single column expression. A correlated subquery is more portable, but
 | 
						|
often performs more poorly at the SQL level. Using the same technique
 | 
						|
illustrated at :ref:`mapper_column_property_sql_expressions`,
 | 
						|
we can adjust our ``SavingsAccount`` example to aggregate the balances for
 | 
						|
*all* accounts, and use a correlated subquery for the column expression::
 | 
						|
 | 
						|
    from sqlalchemy import Column, Integer, ForeignKey, Numeric, String
 | 
						|
    from sqlalchemy.orm import relationship
 | 
						|
    from sqlalchemy.ext.declarative import declarative_base
 | 
						|
    from sqlalchemy.ext.hybrid import hybrid_property
 | 
						|
    from sqlalchemy import select, func
 | 
						|
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class SavingsAccount(Base):
 | 
						|
        __tablename__ = 'account'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
 | 
						|
        balance = Column(Numeric(15, 5))
 | 
						|
 | 
						|
    class User(Base):
 | 
						|
        __tablename__ = 'user'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        name = Column(String(100), nullable=False)
 | 
						|
 | 
						|
        accounts = relationship("SavingsAccount", backref="owner")
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def balance(self):
 | 
						|
            return sum(acc.balance for acc in self.accounts)
 | 
						|
 | 
						|
        @balance.expression
 | 
						|
        def balance(cls):
 | 
						|
            return select(func.sum(SavingsAccount.balance)).\
 | 
						|
                    where(SavingsAccount.user_id==cls.id).\
 | 
						|
                    label('total_balance')
 | 
						|
 | 
						|
The above recipe will give us the ``balance`` column which renders
 | 
						|
a correlated SELECT::
 | 
						|
 | 
						|
    >>> print(s.query(User).filter(User.balance > 400))
 | 
						|
    SELECT "user".id AS user_id, "user".name AS user_name
 | 
						|
    FROM "user"
 | 
						|
    WHERE (SELECT sum(account.balance) AS sum_1
 | 
						|
    FROM account
 | 
						|
    WHERE account.user_id = "user".id) > :param_1
 | 
						|
 | 
						|
.. _hybrid_custom_comparators:
 | 
						|
 | 
						|
Building Custom Comparators
 | 
						|
---------------------------
 | 
						|
 | 
						|
The hybrid property also includes a helper that allows construction of
 | 
						|
custom comparators. A comparator object allows one to customize the
 | 
						|
behavior of each SQLAlchemy expression operator individually.  They
 | 
						|
are useful when creating custom types that have some highly
 | 
						|
idiosyncratic behavior on the SQL side.
 | 
						|
 | 
						|
.. note::  The :meth:`.hybrid_property.comparator` decorator introduced
 | 
						|
   in this section **replaces** the use of the
 | 
						|
   :meth:`.hybrid_property.expression` decorator.
 | 
						|
   They cannot be used together.
 | 
						|
 | 
						|
The example class below allows case-insensitive comparisons on the attribute
 | 
						|
named ``word_insensitive``::
 | 
						|
 | 
						|
    from sqlalchemy.ext.hybrid import Comparator, hybrid_property
 | 
						|
    from sqlalchemy import func, Column, Integer, String
 | 
						|
    from sqlalchemy.orm import Session
 | 
						|
    from sqlalchemy.ext.declarative import declarative_base
 | 
						|
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class CaseInsensitiveComparator(Comparator):
 | 
						|
        def __eq__(self, other):
 | 
						|
            return func.lower(self.__clause_element__()) == func.lower(other)
 | 
						|
 | 
						|
    class SearchWord(Base):
 | 
						|
        __tablename__ = 'searchword'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        word = Column(String(255), nullable=False)
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def word_insensitive(self):
 | 
						|
            return self.word.lower()
 | 
						|
 | 
						|
        @word_insensitive.comparator
 | 
						|
        def word_insensitive(cls):
 | 
						|
            return CaseInsensitiveComparator(cls.word)
 | 
						|
 | 
						|
Above, SQL expressions against ``word_insensitive`` will apply the ``LOWER()``
 | 
						|
SQL function to both sides::
 | 
						|
 | 
						|
    >>> print(Session().query(SearchWord).filter_by(word_insensitive="Trucks"))
 | 
						|
    SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
 | 
						|
    FROM searchword
 | 
						|
    WHERE lower(searchword.word) = lower(:lower_1)
 | 
						|
 | 
						|
The ``CaseInsensitiveComparator`` above implements part of the
 | 
						|
:class:`.ColumnOperators` interface.   A "coercion" operation like
 | 
						|
lowercasing can be applied to all comparison operations (i.e. ``eq``,
 | 
						|
``lt``, ``gt``, etc.) using :meth:`.Operators.operate`::
 | 
						|
 | 
						|
    class CaseInsensitiveComparator(Comparator):
 | 
						|
        def operate(self, op, other):
 | 
						|
            return op(func.lower(self.__clause_element__()), func.lower(other))
 | 
						|
 | 
						|
.. _hybrid_reuse_subclass:
 | 
						|
 | 
						|
Reusing Hybrid Properties across Subclasses
 | 
						|
-------------------------------------------
 | 
						|
 | 
						|
A hybrid can be referred to from a superclass, to allow modifying
 | 
						|
methods like :meth:`.hybrid_property.getter`, :meth:`.hybrid_property.setter`
 | 
						|
to be used to redefine those methods on a subclass.  This is similar to
 | 
						|
how the standard Python ``@property`` object works::
 | 
						|
 | 
						|
    class FirstNameOnly(Base):
 | 
						|
        # ...
 | 
						|
 | 
						|
        first_name = Column(String)
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def name(self):
 | 
						|
            return self.first_name
 | 
						|
 | 
						|
        @name.setter
 | 
						|
        def name(self, value):
 | 
						|
            self.first_name = value
 | 
						|
 | 
						|
    class FirstNameLastName(FirstNameOnly):
 | 
						|
        # ...
 | 
						|
 | 
						|
        last_name = Column(String)
 | 
						|
 | 
						|
        @FirstNameOnly.name.getter
 | 
						|
        def name(self):
 | 
						|
            return self.first_name + ' ' + self.last_name
 | 
						|
 | 
						|
        @name.setter
 | 
						|
        def name(self, value):
 | 
						|
            self.first_name, self.last_name = value.split(' ', 1)
 | 
						|
 | 
						|
Above, the ``FirstNameLastName`` class refers to the hybrid from
 | 
						|
``FirstNameOnly.name`` to repurpose its getter and setter for the subclass.
 | 
						|
 | 
						|
When overriding :meth:`.hybrid_property.expression` and
 | 
						|
:meth:`.hybrid_property.comparator` alone as the first reference to the
 | 
						|
superclass, these names conflict with the same-named accessors on the class-
 | 
						|
level :class:`.QueryableAttribute` object returned at the class level.  To
 | 
						|
override these methods when referring directly to the parent class descriptor,
 | 
						|
add the special qualifier :attr:`.hybrid_property.overrides`, which will de-
 | 
						|
reference the instrumented attribute back to the hybrid object::
 | 
						|
 | 
						|
    class FirstNameLastName(FirstNameOnly):
 | 
						|
        # ...
 | 
						|
 | 
						|
        last_name = Column(String)
 | 
						|
 | 
						|
        @FirstNameOnly.name.overrides.expression
 | 
						|
        def name(cls):
 | 
						|
            return func.concat(cls.first_name, ' ', cls.last_name)
 | 
						|
 | 
						|
.. versionadded:: 1.2 Added :meth:`.hybrid_property.getter` as well as the
 | 
						|
   ability to redefine accessors per-subclass.
 | 
						|
 | 
						|
 | 
						|
Hybrid Value Objects
 | 
						|
--------------------
 | 
						|
 | 
						|
Note in our previous example, if we were to compare the ``word_insensitive``
 | 
						|
attribute of a ``SearchWord`` instance to a plain Python string, the plain
 | 
						|
Python string would not be coerced to lower case - the
 | 
						|
``CaseInsensitiveComparator`` we built, being returned by
 | 
						|
``@word_insensitive.comparator``, only applies to the SQL side.
 | 
						|
 | 
						|
A more comprehensive form of the custom comparator is to construct a *Hybrid
 | 
						|
Value Object*. This technique applies the target value or expression to a value
 | 
						|
object which is then returned by the accessor in all cases.   The value object
 | 
						|
allows control of all operations upon the value as well as how compared values
 | 
						|
are treated, both on the SQL expression side as well as the Python value side.
 | 
						|
Replacing the previous ``CaseInsensitiveComparator`` class with a new
 | 
						|
``CaseInsensitiveWord`` class::
 | 
						|
 | 
						|
    class CaseInsensitiveWord(Comparator):
 | 
						|
        "Hybrid value representing a lower case representation of a word."
 | 
						|
 | 
						|
        def __init__(self, word):
 | 
						|
            if isinstance(word, basestring):
 | 
						|
                self.word = word.lower()
 | 
						|
            elif isinstance(word, CaseInsensitiveWord):
 | 
						|
                self.word = word.word
 | 
						|
            else:
 | 
						|
                self.word = func.lower(word)
 | 
						|
 | 
						|
        def operate(self, op, other):
 | 
						|
            if not isinstance(other, CaseInsensitiveWord):
 | 
						|
                other = CaseInsensitiveWord(other)
 | 
						|
            return op(self.word, other.word)
 | 
						|
 | 
						|
        def __clause_element__(self):
 | 
						|
            return self.word
 | 
						|
 | 
						|
        def __str__(self):
 | 
						|
            return self.word
 | 
						|
 | 
						|
        key = 'word'
 | 
						|
        "Label to apply to Query tuple results"
 | 
						|
 | 
						|
Above, the ``CaseInsensitiveWord`` object represents ``self.word``, which may
 | 
						|
be a SQL function, or may be a Python native.   By overriding ``operate()`` and
 | 
						|
``__clause_element__()`` to work in terms of ``self.word``, all comparison
 | 
						|
operations will work against the "converted" form of ``word``, whether it be
 | 
						|
SQL side or Python side. Our ``SearchWord`` class can now deliver the
 | 
						|
``CaseInsensitiveWord`` object unconditionally from a single hybrid call::
 | 
						|
 | 
						|
    class SearchWord(Base):
 | 
						|
        __tablename__ = 'searchword'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        word = Column(String(255), nullable=False)
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def word_insensitive(self):
 | 
						|
            return CaseInsensitiveWord(self.word)
 | 
						|
 | 
						|
The ``word_insensitive`` attribute now has case-insensitive comparison behavior
 | 
						|
universally, including SQL expression vs. Python expression (note the Python
 | 
						|
value is converted to lower case on the Python side here)::
 | 
						|
 | 
						|
    >>> print(Session().query(SearchWord).filter_by(word_insensitive="Trucks"))
 | 
						|
    SELECT searchword.id AS searchword_id, searchword.word AS searchword_word
 | 
						|
    FROM searchword
 | 
						|
    WHERE lower(searchword.word) = :lower_1
 | 
						|
 | 
						|
SQL expression versus SQL expression::
 | 
						|
 | 
						|
    >>> sw1 = aliased(SearchWord)
 | 
						|
    >>> sw2 = aliased(SearchWord)
 | 
						|
    >>> print(Session().query(
 | 
						|
    ...                    sw1.word_insensitive,
 | 
						|
    ...                    sw2.word_insensitive).\
 | 
						|
    ...                        filter(
 | 
						|
    ...                            sw1.word_insensitive > sw2.word_insensitive
 | 
						|
    ...                        ))
 | 
						|
    SELECT lower(searchword_1.word) AS lower_1,
 | 
						|
    lower(searchword_2.word) AS lower_2
 | 
						|
    FROM searchword AS searchword_1, searchword AS searchword_2
 | 
						|
    WHERE lower(searchword_1.word) > lower(searchword_2.word)
 | 
						|
 | 
						|
Python only expression::
 | 
						|
 | 
						|
    >>> ws1 = SearchWord(word="SomeWord")
 | 
						|
    >>> ws1.word_insensitive == "sOmEwOrD"
 | 
						|
    True
 | 
						|
    >>> ws1.word_insensitive == "XOmEwOrX"
 | 
						|
    False
 | 
						|
    >>> print(ws1.word_insensitive)
 | 
						|
    someword
 | 
						|
 | 
						|
The Hybrid Value pattern is very useful for any kind of value that may have
 | 
						|
multiple representations, such as timestamps, time deltas, units of
 | 
						|
measurement, currencies and encrypted passwords.
 | 
						|
 | 
						|
.. seealso::
 | 
						|
 | 
						|
    `Hybrids and Value Agnostic Types
 | 
						|
    <https://techspot.zzzeek.org/2011/10/21/hybrids-and-value-agnostic-types/>`_
 | 
						|
    - on the techspot.zzzeek.org blog
 | 
						|
 | 
						|
    `Value Agnostic Types, Part II
 | 
						|
    <https://techspot.zzzeek.org/2011/10/29/value-agnostic-types-part-ii/>`_ -
 | 
						|
    on the techspot.zzzeek.org blog
 | 
						|
 | 
						|
.. _hybrid_transformers:
 | 
						|
 | 
						|
Building Transformers
 | 
						|
----------------------
 | 
						|
 | 
						|
A *transformer* is an object which can receive a :class:`_query.Query`
 | 
						|
object and
 | 
						|
return a new one.   The :class:`_query.Query` object includes a method
 | 
						|
:meth:`.with_transformation` that returns a new :class:`_query.Query`
 | 
						|
transformed by
 | 
						|
the given function.
 | 
						|
 | 
						|
We can combine this with the :class:`.Comparator` class to produce one type
 | 
						|
of recipe which can both set up the FROM clause of a query as well as assign
 | 
						|
filtering criterion.
 | 
						|
 | 
						|
Consider a mapped class ``Node``, which assembles using adjacency list into a
 | 
						|
hierarchical tree pattern::
 | 
						|
 | 
						|
    from sqlalchemy import Column, Integer, ForeignKey
 | 
						|
    from sqlalchemy.orm import relationship
 | 
						|
    from sqlalchemy.ext.declarative import declarative_base
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class Node(Base):
 | 
						|
        __tablename__ = 'node'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        parent_id = Column(Integer, ForeignKey('node.id'))
 | 
						|
        parent = relationship("Node", remote_side=id)
 | 
						|
 | 
						|
Suppose we wanted to add an accessor ``grandparent``.  This would return the
 | 
						|
``parent`` of ``Node.parent``.  When we have an instance of ``Node``, this is
 | 
						|
simple::
 | 
						|
 | 
						|
    from sqlalchemy.ext.hybrid import hybrid_property
 | 
						|
 | 
						|
    class Node(Base):
 | 
						|
        # ...
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def grandparent(self):
 | 
						|
            return self.parent.parent
 | 
						|
 | 
						|
For the expression, things are not so clear.   We'd need to construct a
 | 
						|
:class:`_query.Query` where we :meth:`_query.Query.join` twice along
 | 
						|
``Node.parent`` to get to the ``grandparent``.   We can instead return a
 | 
						|
transforming callable that we'll combine with the :class:`.Comparator` class to
 | 
						|
receive any :class:`_query.Query` object, and return a new one that's joined to
 | 
						|
the ``Node.parent`` attribute and filtered based on the given criterion::
 | 
						|
 | 
						|
    from sqlalchemy.ext.hybrid import Comparator
 | 
						|
 | 
						|
    class GrandparentTransformer(Comparator):
 | 
						|
        def operate(self, op, other):
 | 
						|
            def transform(q):
 | 
						|
                cls = self.__clause_element__()
 | 
						|
                parent_alias = aliased(cls)
 | 
						|
                return q.join(parent_alias, cls.parent).\
 | 
						|
                            filter(op(parent_alias.parent, other))
 | 
						|
            return transform
 | 
						|
 | 
						|
    Base = declarative_base()
 | 
						|
 | 
						|
    class Node(Base):
 | 
						|
        __tablename__ = 'node'
 | 
						|
        id = Column(Integer, primary_key=True)
 | 
						|
        parent_id = Column(Integer, ForeignKey('node.id'))
 | 
						|
        parent = relationship("Node", remote_side=id)
 | 
						|
 | 
						|
        @hybrid_property
 | 
						|
        def grandparent(self):
 | 
						|
            return self.parent.parent
 | 
						|
 | 
						|
        @grandparent.comparator
 | 
						|
        def grandparent(cls):
 | 
						|
            return GrandparentTransformer(cls)
 | 
						|
 | 
						|
The ``GrandparentTransformer`` overrides the core :meth:`.Operators.operate`
 | 
						|
method at the base of the :class:`.Comparator` hierarchy to return a query-
 | 
						|
transforming callable, which then runs the given comparison operation in a
 | 
						|
particular context. Such as, in the example above, the ``operate`` method is
 | 
						|
called, given the :attr:`.Operators.eq` callable as well as the right side of
 | 
						|
the comparison ``Node(id=5)``.  A function ``transform`` is then returned which
 | 
						|
will transform a :class:`_query.Query` first to join to ``Node.parent``,
 | 
						|
then to
 | 
						|
compare ``parent_alias`` using :attr:`.Operators.eq` against the left and right
 | 
						|
sides, passing into :meth:`_query.Query.filter`:
 | 
						|
 | 
						|
.. sourcecode:: pycon+sql
 | 
						|
 | 
						|
    >>> from sqlalchemy.orm import Session
 | 
						|
    >>> session = Session()
 | 
						|
    {sql}>>> session.query(Node).\
 | 
						|
    ...        with_transformation(Node.grandparent==Node(id=5)).\
 | 
						|
    ...        all()
 | 
						|
    SELECT node.id AS node_id, node.parent_id AS node_parent_id
 | 
						|
    FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
 | 
						|
    WHERE :param_1 = node_1.parent_id
 | 
						|
    {stop}
 | 
						|
 | 
						|
We can modify the pattern to be more verbose but flexible by separating the
 | 
						|
"join" step from the "filter" step.  The tricky part here is ensuring that
 | 
						|
successive instances of ``GrandparentTransformer`` use the same
 | 
						|
:class:`.AliasedClass` object against ``Node``.  Below we use a simple
 | 
						|
memoizing approach that associates a ``GrandparentTransformer`` with each
 | 
						|
class::
 | 
						|
 | 
						|
    class Node(Base):
 | 
						|
 | 
						|
        # ...
 | 
						|
 | 
						|
        @grandparent.comparator
 | 
						|
        def grandparent(cls):
 | 
						|
            # memoize a GrandparentTransformer
 | 
						|
            # per class
 | 
						|
            if '_gp' not in cls.__dict__:
 | 
						|
                cls._gp = GrandparentTransformer(cls)
 | 
						|
            return cls._gp
 | 
						|
 | 
						|
    class GrandparentTransformer(Comparator):
 | 
						|
 | 
						|
        def __init__(self, cls):
 | 
						|
            self.parent_alias = aliased(cls)
 | 
						|
 | 
						|
        @property
 | 
						|
        def join(self):
 | 
						|
            def go(q):
 | 
						|
                return q.join(self.parent_alias, Node.parent)
 | 
						|
            return go
 | 
						|
 | 
						|
        def operate(self, op, other):
 | 
						|
            return op(self.parent_alias.parent, other)
 | 
						|
 | 
						|
.. sourcecode:: pycon+sql
 | 
						|
 | 
						|
    {sql}>>> session.query(Node).\
 | 
						|
    ...            with_transformation(Node.grandparent.join).\
 | 
						|
    ...            filter(Node.grandparent==Node(id=5))
 | 
						|
    SELECT node.id AS node_id, node.parent_id AS node_parent_id
 | 
						|
    FROM node JOIN node AS node_1 ON node_1.id = node.parent_id
 | 
						|
    WHERE :param_1 = node_1.parent_id
 | 
						|
    {stop}
 | 
						|
 | 
						|
The "transformer" pattern is an experimental pattern that starts to make usage
 | 
						|
of some functional programming paradigms. While it's only recommended for
 | 
						|
advanced and/or patient developers, there's probably a whole lot of amazing
 | 
						|
things it can be used for.
 | 
						|
 | 
						|
"""  # noqa
 | 
						|
from .. import util
 | 
						|
from ..orm import attributes
 | 
						|
from ..orm import interfaces
 | 
						|
 | 
						|
HYBRID_METHOD = util.symbol("HYBRID_METHOD")
 | 
						|
"""Symbol indicating an :class:`InspectionAttr` that's
 | 
						|
   of type :class:`.hybrid_method`.
 | 
						|
 | 
						|
   Is assigned to the :attr:`.InspectionAttr.extension_type`
 | 
						|
   attribute.
 | 
						|
 | 
						|
   .. seealso::
 | 
						|
 | 
						|
    :attr:`_orm.Mapper.all_orm_attributes`
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
HYBRID_PROPERTY = util.symbol("HYBRID_PROPERTY")
 | 
						|
"""Symbol indicating an :class:`InspectionAttr` that's
 | 
						|
    of type :class:`.hybrid_method`.
 | 
						|
 | 
						|
   Is assigned to the :attr:`.InspectionAttr.extension_type`
 | 
						|
   attribute.
 | 
						|
 | 
						|
   .. seealso::
 | 
						|
 | 
						|
    :attr:`_orm.Mapper.all_orm_attributes`
 | 
						|
 | 
						|
"""
 | 
						|
 | 
						|
 | 
						|
class hybrid_method(interfaces.InspectionAttrInfo):
 | 
						|
    """A decorator which allows definition of a Python object method with both
 | 
						|
    instance-level and class-level behavior.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    is_attribute = True
 | 
						|
    extension_type = HYBRID_METHOD
 | 
						|
 | 
						|
    def __init__(self, func, expr=None):
 | 
						|
        """Create a new :class:`.hybrid_method`.
 | 
						|
 | 
						|
        Usage is typically via decorator::
 | 
						|
 | 
						|
            from sqlalchemy.ext.hybrid import hybrid_method
 | 
						|
 | 
						|
            class SomeClass(object):
 | 
						|
                @hybrid_method
 | 
						|
                def value(self, x, y):
 | 
						|
                    return self._value + x + y
 | 
						|
 | 
						|
                @value.expression
 | 
						|
                def value(self, x, y):
 | 
						|
                    return func.some_function(self._value, x, y)
 | 
						|
 | 
						|
        """
 | 
						|
        self.func = func
 | 
						|
        self.expression(expr or func)
 | 
						|
 | 
						|
    def __get__(self, instance, owner):
 | 
						|
        if instance is None:
 | 
						|
            return self.expr.__get__(owner, owner.__class__)
 | 
						|
        else:
 | 
						|
            return self.func.__get__(instance, owner)
 | 
						|
 | 
						|
    def expression(self, expr):
 | 
						|
        """Provide a modifying decorator that defines a
 | 
						|
        SQL-expression producing method."""
 | 
						|
 | 
						|
        self.expr = expr
 | 
						|
        if not self.expr.__doc__:
 | 
						|
            self.expr.__doc__ = self.func.__doc__
 | 
						|
        return self
 | 
						|
 | 
						|
 | 
						|
class hybrid_property(interfaces.InspectionAttrInfo):
 | 
						|
    """A decorator which allows definition of a Python descriptor with both
 | 
						|
    instance-level and class-level behavior.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    is_attribute = True
 | 
						|
    extension_type = HYBRID_PROPERTY
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        fget,
 | 
						|
        fset=None,
 | 
						|
        fdel=None,
 | 
						|
        expr=None,
 | 
						|
        custom_comparator=None,
 | 
						|
        update_expr=None,
 | 
						|
    ):
 | 
						|
        """Create a new :class:`.hybrid_property`.
 | 
						|
 | 
						|
        Usage is typically via decorator::
 | 
						|
 | 
						|
            from sqlalchemy.ext.hybrid import hybrid_property
 | 
						|
 | 
						|
            class SomeClass(object):
 | 
						|
                @hybrid_property
 | 
						|
                def value(self):
 | 
						|
                    return self._value
 | 
						|
 | 
						|
                @value.setter
 | 
						|
                def value(self, value):
 | 
						|
                    self._value = value
 | 
						|
 | 
						|
        """
 | 
						|
        self.fget = fget
 | 
						|
        self.fset = fset
 | 
						|
        self.fdel = fdel
 | 
						|
        self.expr = expr
 | 
						|
        self.custom_comparator = custom_comparator
 | 
						|
        self.update_expr = update_expr
 | 
						|
        util.update_wrapper(self, fget)
 | 
						|
 | 
						|
    def __get__(self, instance, owner):
 | 
						|
        if instance is None:
 | 
						|
            return self._expr_comparator(owner)
 | 
						|
        else:
 | 
						|
            return self.fget(instance)
 | 
						|
 | 
						|
    def __set__(self, instance, value):
 | 
						|
        if self.fset is None:
 | 
						|
            raise AttributeError("can't set attribute")
 | 
						|
        self.fset(instance, value)
 | 
						|
 | 
						|
    def __delete__(self, instance):
 | 
						|
        if self.fdel is None:
 | 
						|
            raise AttributeError("can't delete attribute")
 | 
						|
        self.fdel(instance)
 | 
						|
 | 
						|
    def _copy(self, **kw):
 | 
						|
        defaults = {
 | 
						|
            key: value
 | 
						|
            for key, value in self.__dict__.items()
 | 
						|
            if not key.startswith("_")
 | 
						|
        }
 | 
						|
        defaults.update(**kw)
 | 
						|
        return type(self)(**defaults)
 | 
						|
 | 
						|
    @property
 | 
						|
    def overrides(self):
 | 
						|
        """Prefix for a method that is overriding an existing attribute.
 | 
						|
 | 
						|
        The :attr:`.hybrid_property.overrides` accessor just returns
 | 
						|
        this hybrid object, which when called at the class level from
 | 
						|
        a parent class, will de-reference the "instrumented attribute"
 | 
						|
        normally returned at this level, and allow modifying decorators
 | 
						|
        like :meth:`.hybrid_property.expression` and
 | 
						|
        :meth:`.hybrid_property.comparator`
 | 
						|
        to be used without conflicting with the same-named attributes
 | 
						|
        normally present on the :class:`.QueryableAttribute`::
 | 
						|
 | 
						|
            class SuperClass(object):
 | 
						|
                # ...
 | 
						|
 | 
						|
                @hybrid_property
 | 
						|
                def foobar(self):
 | 
						|
                    return self._foobar
 | 
						|
 | 
						|
            class SubClass(SuperClass):
 | 
						|
                # ...
 | 
						|
 | 
						|
                @SuperClass.foobar.overrides.expression
 | 
						|
                def foobar(cls):
 | 
						|
                    return func.subfoobar(self._foobar)
 | 
						|
 | 
						|
        .. versionadded:: 1.2
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`hybrid_reuse_subclass`
 | 
						|
 | 
						|
        """
 | 
						|
        return self
 | 
						|
 | 
						|
    def getter(self, fget):
 | 
						|
        """Provide a modifying decorator that defines a getter method.
 | 
						|
 | 
						|
        .. versionadded:: 1.2
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        return self._copy(fget=fget)
 | 
						|
 | 
						|
    def setter(self, fset):
 | 
						|
        """Provide a modifying decorator that defines a setter method."""
 | 
						|
 | 
						|
        return self._copy(fset=fset)
 | 
						|
 | 
						|
    def deleter(self, fdel):
 | 
						|
        """Provide a modifying decorator that defines a deletion method."""
 | 
						|
 | 
						|
        return self._copy(fdel=fdel)
 | 
						|
 | 
						|
    def expression(self, expr):
 | 
						|
        """Provide a modifying decorator that defines a SQL-expression
 | 
						|
        producing method.
 | 
						|
 | 
						|
        When a hybrid is invoked at the class level, the SQL expression given
 | 
						|
        here is wrapped inside of a specialized :class:`.QueryableAttribute`,
 | 
						|
        which is the same kind of object used by the ORM to represent other
 | 
						|
        mapped attributes.   The reason for this is so that other class-level
 | 
						|
        attributes such as docstrings and a reference to the hybrid itself may
 | 
						|
        be maintained within the structure that's returned, without any
 | 
						|
        modifications to the original SQL expression passed in.
 | 
						|
 | 
						|
        .. note::
 | 
						|
 | 
						|
           When referring to a hybrid property  from an owning class (e.g.
 | 
						|
           ``SomeClass.some_hybrid``), an instance of
 | 
						|
           :class:`.QueryableAttribute` is returned, representing the
 | 
						|
           expression or comparator object as well as this  hybrid object.
 | 
						|
           However, that object itself has accessors called ``expression`` and
 | 
						|
           ``comparator``; so when attempting to override these decorators on a
 | 
						|
           subclass, it may be necessary to qualify it using the
 | 
						|
           :attr:`.hybrid_property.overrides` modifier first.  See that
 | 
						|
           modifier for details.
 | 
						|
 | 
						|
        .. seealso::
 | 
						|
 | 
						|
            :ref:`hybrid_distinct_expression`
 | 
						|
 | 
						|
        """
 | 
						|
 | 
						|
        return self._copy(expr=expr)
 | 
						|
 | 
						|
    def comparator(self, comparator):
 | 
						|
        """Provide a modifying decorator that defines a custom
 | 
						|
        comparator producing method.
 | 
						|
 | 
						|
        The return value of the decorated method should be an instance of
 | 
						|
        :class:`~.hybrid.Comparator`.
 | 
						|
 | 
						|
        .. note::  The :meth:`.hybrid_property.comparator` decorator
 | 
						|
           **replaces** the use of the :meth:`.hybrid_property.expression`
 | 
						|
           decorator.  They cannot be used together.
 | 
						|
 | 
						|
        When a hybrid is invoked at the class level, the
 | 
						|
        :class:`~.hybrid.Comparator` object given here is wrapped inside of a
 | 
						|
        specialized :class:`.QueryableAttribute`, which is the same kind of
 | 
						|
        object used by the ORM to represent other mapped attributes.   The
 | 
						|
        reason for this is so that other class-level attributes such as
 | 
						|
        docstrings and a reference to the hybrid itself may be maintained
 | 
						|
        within the structure that's returned, without any modifications to the
 | 
						|
        original comparator object passed in.
 | 
						|
 | 
						|
        .. note::
 | 
						|
 | 
						|
           When referring to a hybrid property  from an owning class (e.g.
 | 
						|
           ``SomeClass.some_hybrid``), an instance of
 | 
						|
           :class:`.QueryableAttribute` is returned, representing the
 | 
						|
           expression or comparator object as this  hybrid object.  However,
 | 
						|
           that object itself has accessors called ``expression`` and
 | 
						|
           ``comparator``; so when attempting to override these decorators on a
 | 
						|
           subclass, it may be necessary to qualify it using the
 | 
						|
           :attr:`.hybrid_property.overrides` modifier first.  See that
 | 
						|
           modifier for details.
 | 
						|
 | 
						|
        """
 | 
						|
        return self._copy(custom_comparator=comparator)
 | 
						|
 | 
						|
    def update_expression(self, meth):
 | 
						|
        """Provide a modifying decorator that defines an UPDATE tuple
 | 
						|
        producing method.
 | 
						|
 | 
						|
        The method accepts a single value, which is the value to be
 | 
						|
        rendered into the SET clause of an UPDATE statement.  The method
 | 
						|
        should then process this value into individual column expressions
 | 
						|
        that fit into the ultimate SET clause, and return them as a
 | 
						|
        sequence of 2-tuples.  Each tuple
 | 
						|
        contains a column expression as the key and a value to be rendered.
 | 
						|
 | 
						|
        E.g.::
 | 
						|
 | 
						|
            class Person(Base):
 | 
						|
                # ...
 | 
						|
 | 
						|
                first_name = Column(String)
 | 
						|
                last_name = Column(String)
 | 
						|
 | 
						|
                @hybrid_property
 | 
						|
                def fullname(self):
 | 
						|
                    return first_name + " " + last_name
 | 
						|
 | 
						|
                @fullname.update_expression
 | 
						|
                def fullname(cls, value):
 | 
						|
                    fname, lname = value.split(" ", 1)
 | 
						|
                    return [
 | 
						|
                        (cls.first_name, fname),
 | 
						|
                        (cls.last_name, lname)
 | 
						|
                    ]
 | 
						|
 | 
						|
        .. versionadded:: 1.2
 | 
						|
 | 
						|
        """
 | 
						|
        return self._copy(update_expr=meth)
 | 
						|
 | 
						|
    @util.memoized_property
 | 
						|
    def _expr_comparator(self):
 | 
						|
        if self.custom_comparator is not None:
 | 
						|
            return self._get_comparator(self.custom_comparator)
 | 
						|
        elif self.expr is not None:
 | 
						|
            return self._get_expr(self.expr)
 | 
						|
        else:
 | 
						|
            return self._get_expr(self.fget)
 | 
						|
 | 
						|
    def _get_expr(self, expr):
 | 
						|
        def _expr(cls):
 | 
						|
            return ExprComparator(cls, expr(cls), self)
 | 
						|
 | 
						|
        util.update_wrapper(_expr, expr)
 | 
						|
 | 
						|
        return self._get_comparator(_expr)
 | 
						|
 | 
						|
    def _get_comparator(self, comparator):
 | 
						|
 | 
						|
        proxy_attr = attributes.create_proxied_attribute(self)
 | 
						|
 | 
						|
        def expr_comparator(owner):
 | 
						|
            # because this is the descriptor protocol, we don't really know
 | 
						|
            # what our attribute name is.  so search for it through the
 | 
						|
            # MRO.
 | 
						|
            for lookup in owner.__mro__:
 | 
						|
                if self.__name__ in lookup.__dict__:
 | 
						|
                    if lookup.__dict__[self.__name__] is self:
 | 
						|
                        name = self.__name__
 | 
						|
                        break
 | 
						|
            else:
 | 
						|
                name = attributes.NO_KEY
 | 
						|
 | 
						|
            return proxy_attr(
 | 
						|
                owner,
 | 
						|
                name,
 | 
						|
                self,
 | 
						|
                comparator(owner),
 | 
						|
                doc=comparator.__doc__ or self.__doc__,
 | 
						|
            )
 | 
						|
 | 
						|
        return expr_comparator
 | 
						|
 | 
						|
 | 
						|
class Comparator(interfaces.PropComparator):
 | 
						|
    """A helper class that allows easy construction of custom
 | 
						|
    :class:`~.orm.interfaces.PropComparator`
 | 
						|
    classes for usage with hybrids."""
 | 
						|
 | 
						|
    property = None
 | 
						|
 | 
						|
    def __init__(self, expression):
 | 
						|
        self.expression = expression
 | 
						|
 | 
						|
    def __clause_element__(self):
 | 
						|
        expr = self.expression
 | 
						|
        if hasattr(expr, "__clause_element__"):
 | 
						|
            expr = expr.__clause_element__()
 | 
						|
        return expr
 | 
						|
 | 
						|
    def adapt_to_entity(self, adapt_to_entity):
 | 
						|
        # interesting....
 | 
						|
        return self
 | 
						|
 | 
						|
 | 
						|
class ExprComparator(Comparator):
 | 
						|
    def __init__(self, cls, expression, hybrid):
 | 
						|
        self.cls = cls
 | 
						|
        self.expression = expression
 | 
						|
        self.hybrid = hybrid
 | 
						|
 | 
						|
    def __getattr__(self, key):
 | 
						|
        return getattr(self.expression, key)
 | 
						|
 | 
						|
    @property
 | 
						|
    def info(self):
 | 
						|
        return self.hybrid.info
 | 
						|
 | 
						|
    def _bulk_update_tuples(self, value):
 | 
						|
        if isinstance(self.expression, attributes.QueryableAttribute):
 | 
						|
            return self.expression._bulk_update_tuples(value)
 | 
						|
        elif self.hybrid.update_expr is not None:
 | 
						|
            return self.hybrid.update_expr(self.cls, value)
 | 
						|
        else:
 | 
						|
            return [(self.expression, value)]
 | 
						|
 | 
						|
    @property
 | 
						|
    def property(self):
 | 
						|
        return self.expression.property
 | 
						|
 | 
						|
    def operate(self, op, *other, **kwargs):
 | 
						|
        return op(self.expression, *other, **kwargs)
 | 
						|
 | 
						|
    def reverse_operate(self, op, other, **kwargs):
 | 
						|
        return op(other, self.expression, **kwargs)
 |