summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/hybrid.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/ext/hybrid.py')
-rw-r--r--lib/sqlalchemy/ext/hybrid.py109
1 files changed, 105 insertions, 4 deletions
diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py
index 17049d995..141a64599 100644
--- a/lib/sqlalchemy/ext/hybrid.py
+++ b/lib/sqlalchemy/ext/hybrid.py
@@ -183,6 +183,62 @@ The ``length(self, value)`` method is now called upon set::
>>> i1.end
17
+.. _hybrid_bulk_update:
+
+Allowing Bulk ORM Update
+------------------------
+
+A hybrid can define a custom "UPDATE" handler for when using the
+:meth:`.Query.update` method, allowing the hybrid to be used in the
+SET clause of the update.
+
+Normally, when using a hybrid with :meth:`.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.update` which can affect
+this, using the :meth:`.hybrid_propery.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.
+
+.. versionadded:: 1.2 added support for bulk updates to hybrid properties.
+
Working with Relationships
--------------------------
@@ -777,7 +833,7 @@ class hybrid_property(interfaces.InspectionAttrInfo):
def __init__(
self, fget, fset=None, fdel=None,
- expr=None, custom_comparator=None):
+ expr=None, custom_comparator=None, update_expr=None):
"""Create a new :class:`.hybrid_property`.
Usage is typically via decorator::
@@ -799,7 +855,7 @@ class hybrid_property(interfaces.InspectionAttrInfo):
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):
@@ -940,6 +996,42 @@ class hybrid_property(interfaces.InspectionAttrInfo):
"""
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:
@@ -952,7 +1044,7 @@ class hybrid_property(interfaces.InspectionAttrInfo):
def _get_expr(self, expr):
def _expr(cls):
- return ExprComparator(expr(cls), self)
+ return ExprComparator(cls, expr(cls), self)
util.update_wrapper(_expr, expr)
return self._get_comparator(_expr)
@@ -990,7 +1082,8 @@ class Comparator(interfaces.PropComparator):
class ExprComparator(Comparator):
- def __init__(self, expression, hybrid):
+ def __init__(self, cls, expression, hybrid):
+ self.cls = cls
self.expression = expression
self.hybrid = hybrid
@@ -1001,6 +1094,14 @@ class ExprComparator(Comparator):
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