From: Mike Bayer Date: Sat, 20 Nov 2010 20:43:12 +0000 (-0500) Subject: - bindparam() gets a new option "callable", which is a lambda or def X-Git-Tag: rel_0_7b1~243 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ea55a4644c63358324ce4066c501c048c7a661a6;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - bindparam() gets a new option "callable", which is a lambda or def 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] --- diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 02e883de41..8c98adf8c5 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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]) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 1c316c3c04..8be34da240 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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]) diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 6f0a45df92..8ca510e131 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 0f93643dc7..ede3235c59 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -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 diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index e2a5ba1444..7dbb6fc6f3 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -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']),