summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/operators.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/sql/operators.py')
-rw-r--r--lib/sqlalchemy/sql/operators.py70
1 files changed, 65 insertions, 5 deletions
diff --git a/lib/sqlalchemy/sql/operators.py b/lib/sqlalchemy/sql/operators.py
index 74eb73e46..8006d6145 100644
--- a/lib/sqlalchemy/sql/operators.py
+++ b/lib/sqlalchemy/sql/operators.py
@@ -31,6 +31,7 @@ from operator import rshift
from operator import sub
from operator import truediv
+from .. import exc
from .. import util
@@ -117,7 +118,12 @@ class Operators:
return self.operate(inv)
def op(
- self, opstring, precedence=0, is_comparison=False, return_type=None
+ self,
+ opstring,
+ precedence=0,
+ is_comparison=False,
+ return_type=None,
+ python_impl=None,
):
"""Produce a generic operator function.
@@ -164,6 +170,26 @@ class Operators:
:class:`.Boolean`, and those that do not will be of the same
type as the left-hand operand.
+ :param python_impl: an optional Python function that can evaluate
+ two Python values in the same way as this operator works when
+ run on the database server. Useful for in-Python SQL expression
+ evaluation functions, such as for ORM hybrid attributes, and the
+ ORM "evaluator" used to match objects in a session after a multi-row
+ update or delete.
+
+ e.g.::
+
+ >>> expr = column('x').op('+', python_impl=lambda a, b: a + b)('y')
+
+ The operator for the above expression will also work for non-SQL
+ left and right objects::
+
+ >>> expr.operator(5, 10)
+ 15
+
+ .. versionadded:: 2.0
+
+
.. seealso::
:ref:`types_operators`
@@ -171,14 +197,20 @@ class Operators:
:ref:`relationship_custom_operator`
"""
- operator = custom_op(opstring, precedence, is_comparison, return_type)
+ operator = custom_op(
+ opstring,
+ precedence,
+ is_comparison,
+ return_type,
+ python_impl=python_impl,
+ )
def against(other):
return operator(self, other)
return against
- def bool_op(self, opstring, precedence=0):
+ def bool_op(self, opstring, precedence=0, python_impl=None):
"""Return a custom boolean operator.
This method is shorthand for calling
@@ -191,7 +223,12 @@ class Operators:
:meth:`.Operators.op`
"""
- return self.op(opstring, precedence=precedence, is_comparison=True)
+ return self.op(
+ opstring,
+ precedence=precedence,
+ is_comparison=True,
+ python_impl=python_impl,
+ )
def operate(self, op, *other, **kwargs):
r"""Operate on an argument.
@@ -219,6 +256,8 @@ class Operators:
"""
raise NotImplementedError(str(op))
+ __sa_operate__ = operate
+
def reverse_operate(self, op, other, **kwargs):
"""Reverse operate on an argument.
@@ -256,6 +295,16 @@ class custom_op:
__name__ = "custom_op"
+ __slots__ = (
+ "opstring",
+ "precedence",
+ "is_comparison",
+ "natural_self_precedent",
+ "eager_grouping",
+ "return_type",
+ "python_impl",
+ )
+
def __init__(
self,
opstring,
@@ -264,6 +313,7 @@ class custom_op:
return_type=None,
natural_self_precedent=False,
eager_grouping=False,
+ python_impl=None,
):
self.opstring = opstring
self.precedence = precedence
@@ -273,6 +323,7 @@ class custom_op:
self.return_type = (
return_type._to_instance(return_type) if return_type else None
)
+ self.python_impl = python_impl
def __eq__(self, other):
return isinstance(other, custom_op) and other.opstring == self.opstring
@@ -281,7 +332,16 @@ class custom_op:
return id(self)
def __call__(self, left, right, **kw):
- return left.operate(self, right, **kw)
+ if hasattr(left, "__sa_operate__"):
+ return left.operate(self, right, **kw)
+ elif self.python_impl:
+ return self.python_impl(left, right, **kw)
+ else:
+ raise exc.InvalidRequestError(
+ f"Custom operator {self.opstring!r} can't be used with "
+ "plain Python objects unless it includes the "
+ "'python_impl' parameter."
+ )
class ColumnOperators(Operators):