]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- bindparam() gets a new option "callable", which is a lambda or def
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Nov 2010 20:43:12 +0000 (15:43 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 20 Nov 2010 20:43:12 +0000 (15:43 -0500)
evaluated at execution time to determine the value.  This replaces
the implicit recognition of callables sent as the primary value
of bindparam(), which was an undocumented behavior used by the ORM.
The argument is separated now so that values can be passed to
bindparams that are also callables without ambiguity, such
as user defined objects that include a __call__() method.
[ticket:1950]

lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/expression.py
test/sql/test_compiler.py

index 02e883de4166bb55b25c164369bbaa8c7f0b68b4..8c98adf8c5fb7711ba1f3005fdd688cab644c637 100644 (file)
@@ -686,11 +686,11 @@ class RelationshipProperty(StrategizedProperty):
             if self.property.direction == MANYTOONE:
                 state = attributes.instance_state(other)
                 
-                def state_bindparam(state, col):
+                def state_bindparam(x, state, col):
                     o = state.obj() # strong ref
-                    return lambda : \
+                    return sql.bindparam(x, unique=True, callable_=lambda : \
                         self.property.mapper._get_committed_attr_by_column(o,
-                            col)
+                            col))
                 
                 def adapt(col):
                     if self.adapter:
@@ -701,7 +701,7 @@ class RelationshipProperty(StrategizedProperty):
                 if self.property._use_get:
                     return sql.and_(*[
                         sql.or_(
-                        adapt(x) != state_bindparam(state, y),
+                        adapt(x) != state_bindparam(adapt(x), state, y),
                         adapt(x) == None)
                         for (x, y) in self.property.local_remote_pairs])
                     
index 1c316c3c04470f4e5eabbdab2199fd3aa4fa8499..8be34da2406f3b9a1bcac8d80f703305fd1b8c76 100644 (file)
@@ -439,13 +439,13 @@ class LazyLoader(AbstractRelationshipLoader):
         if sess is not None and sess._flushing:
             def visit_bindparam(bindparam):
                 if bindparam.key in bind_to_col:
-                    bindparam.value = \
+                    bindparam.callable = \
                                 lambda: mapper._get_committed_state_attr_by_column(
                                         state, dict_, bind_to_col[bindparam.key])
         else:
             def visit_bindparam(bindparam):
                 if bindparam.key in bind_to_col:
-                    bindparam.value = lambda: mapper._get_state_attr_by_column(
+                    bindparam.callable = lambda: mapper._get_state_attr_by_column(
                                             state, dict_, bind_to_col[bindparam.key])
         
             
index 6f0a45df9253c96259c1e4f88886d83c740d7b21..8ca510e131ae5da68ea7c5a2edd0d4cab9c23f6c 100644 (file)
@@ -279,16 +279,16 @@ class SQLCompiler(engine.Compiled):
                             raise exc.InvalidRequestError(
                                             "A value is required for bind parameter %r" 
                                             % bindparam.key)
-                    elif util.callable(bindparam.value):
-                        pd[name] = bindparam.value()
+                    elif bindparam.callable:
+                        pd[name] = bindparam.callable()
                     else:
                         pd[name] = bindparam.value
             return pd
         else:
             pd = {}
             for bindparam in self.bind_names:
-                if util.callable(bindparam.value):
-                    pd[self.bind_names[bindparam]] = bindparam.value()
+                if bindparam.callable:
+                    pd[self.bind_names[bindparam]] = bindparam.callable()
                 else:
                     pd[self.bind_names[bindparam]] = bindparam.value
             return pd
index 0f93643dc7701f9e884667ee8d945fe4623943da..ede3235c5996d32ec498fe35394c28f6ba67ea6a 100644 (file)
@@ -791,31 +791,51 @@ def table(name, *columns):
     """
     return TableClause(name, *columns)
 
-def bindparam(key, value=None, type_=None, unique=False, required=False):
+def bindparam(key, value=None, type_=None, unique=False, required=False, callable_=None):
     """Create a bind parameter clause with the given key.
 
-    value
-      a default value for this bind parameter.  a bindparam with a
-      value is called a ``value-based bindparam``.
-
-    type\_
-      a sqlalchemy.types.TypeEngine object indicating the type of this
-      bind param, will invoke type-specific bind parameter processing
+        :param key:
+          the key for this bind param.  Will be used in the generated
+          SQL statement for dialects that use named parameters.  This
+          value may be modified when part of a compilation operation,
+          if other :class:`_BindParamClause` objects exist with the same
+          key, or if its length is too long and truncation is
+          required.
 
-    unique
-      if True, bind params sharing the same name will have their
-      underlying ``key`` modified to a uniquely generated name.
-      mostly useful with value-based bind params.
+        :param value:
+          Initial value for this bind param.  This value may be
+          overridden by the dictionary of parameters sent to statement
+          compilation/execution.
+        
+        :param callable\_:
+          A callable function that takes the place of "value".  The function
+          will be called at statement execution time to determine the
+          ultimate value.   Used for scenarios where the actual bind
+          value cannot be determined at the point at which the clause
+          construct is created, but embeded bind values are still desirable.
+          
+        :param type\_:
+          A ``TypeEngine`` object that will be used to pre-process the
+          value corresponding to this :class:`_BindParamClause` at
+          execution time.
 
-    required
-      A value is required at execution time.
-      
+        :param unique:
+          if True, the key name of this BindParamClause will be
+          modified if another :class:`_BindParamClause` of the same name
+          already has been located within the containing
+          :class:`ClauseElement`.
+        
+        :param required:
+          a value is required at execution time.
+          
     """
     if isinstance(key, ColumnClause):
         return _BindParamClause(key.name, value, type_=key.type, 
+                                callable_=callable_,
                                 unique=unique, required=required)
     else:
         return _BindParamClause(key, value, type_=type_, 
+                                callable_=callable_,
                                 unique=unique, required=required)
 
 def outparam(key, type_=None):
@@ -2315,6 +2335,7 @@ class _BindParamClause(ColumnElement):
     quote = None
 
     def __init__(self, key, value, type_=None, unique=False, 
+                            callable_=None,
                             isoutparam=False, required=False, 
                             _compared_to_operator=None,
                             _compared_to_type=None):
@@ -2332,7 +2353,14 @@ class _BindParamClause(ColumnElement):
           Initial value for this bind param.  This value may be
           overridden by the dictionary of parameters sent to statement
           compilation/execution.
-
+        
+        :param callable\_:
+          A callable function that takes the place of "value".  The function
+          will be called at statement execution time to determine the
+          ultimate value.   Used for scenarios where the actual bind
+          value cannot be determined at the point at which the clause
+          construct is created, but embeded bind values are still desirable.
+          
         :param type\_:
           A ``TypeEngine`` object that will be used to pre-process the
           value corresponding to this :class:`_BindParamClause` at
@@ -2361,6 +2389,7 @@ class _BindParamClause(ColumnElement):
         self._orig_key = key or 'param'
         self.unique = unique
         self.value = value
+        self.callable = callable_
         self.isoutparam = isoutparam
         self.required = required
         if type_ is None:
@@ -2405,8 +2434,9 @@ class _BindParamClause(ColumnElement):
 
         d = self.__dict__.copy()
         v = self.value
-        if util.callable(v):
-            v = v()
+        if self.callable:
+            v = self.callable()
+            d['callable'] = None
         d['value'] = v
         return d
 
index e2a5ba1444d5a2c524e6fc682bdb1d563fa5d4b5..7dbb6fc6f32dcc4a83e9cc2c9abf0bb7dc5b599f 100644 (file)
@@ -1808,7 +1808,15 @@ class SelectTest(TestBase, AssertsCompiledSQL):
         
         assert [str(c) for c in s.c] == ["id", "hoho"]
 
-    
+    def test_bind_callable(self):
+        expr = column('x') == bindparam("key", callable_=lambda: 12)
+        self.assert_compile(
+            expr,
+            "x = :key",
+            {'x':12}
+        )
+        
+        
     @testing.emits_warning('.*empty sequence.*')
     def test_in(self):
         self.assert_compile(table1.c.myid.in_(['a']),