diff options
Diffstat (limited to 'lib/sqlalchemy/sql/coercions.py')
-rw-r--r-- | lib/sqlalchemy/sql/coercions.py | 86 |
1 files changed, 84 insertions, 2 deletions
diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 4c6a0317a..be412c770 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -21,9 +21,11 @@ if util.TYPE_CHECKING: from types import ModuleType elements = None # type: ModuleType +lambdas = None # type: ModuleType schema = None # type: ModuleType selectable = None # type: ModuleType sqltypes = None # type: ModuleType +traversals = None # type: ModuleType def _is_literal(element): @@ -51,6 +53,23 @@ def _document_text_coercion(paramname, meth_rst, param_rst): def expect(role, element, apply_propagate_attrs=None, argname=None, **kw): + if ( + role.allows_lambda + # note callable() will not invoke a __getattr__() method, whereas + # hasattr(obj, "__call__") will. by keeping the callable() check here + # we prevent most needless calls to hasattr() and therefore + # __getattr__(), which is present on ColumnElement. + and callable(element) + and hasattr(element, "__code__") + ): + return lambdas.LambdaElement( + element, + role, + apply_propagate_attrs=apply_propagate_attrs, + argname=argname, + **kw + ) + # major case is that we are given a ClauseElement already, skip more # elaborate logic up front if possible impl = _impl_lookup[role] @@ -106,7 +125,12 @@ def expect(role, element, apply_propagate_attrs=None, argname=None, **kw): if impl._role_class in resolved.__class__.__mro__: if impl._post_coercion: - resolved = impl._post_coercion(resolved, argname=argname, **kw) + resolved = impl._post_coercion( + resolved, + argname=argname, + original_element=original_element, + **kw + ) return resolved else: return impl._implicit_coercions( @@ -230,6 +254,8 @@ class _ColumnCoercions(object): ): self._warn_for_scalar_subquery_coercion() return resolved.element.scalar_subquery() + elif self._role_class.allows_lambda and resolved._is_lambda_element: + return resolved else: self._raise_for_expected(original_element, argname, resolved) @@ -319,6 +345,21 @@ class _SelectIsNotFrom(object): ) +class HasCacheKeyImpl(RoleImpl): + __slots__ = () + + def _implicit_coercions( + self, original_element, resolved, argname=None, **kw + ): + if isinstance(original_element, traversals.HasCacheKey): + return original_element + else: + self._raise_for_expected(original_element, argname, resolved) + + def _literal_coercion(self, element, **kw): + return element + + class ExpressionElementImpl(_ColumnCoercions, RoleImpl): __slots__ = () @@ -420,7 +461,14 @@ class InElementImpl(RoleImpl): assert not len(element.clauses) == 0 return element.self_group(against=operator) - elif isinstance(element, elements.BindParameter) and element.expanding: + elif isinstance(element, elements.BindParameter): + if not element.expanding: + # coercing to expanding at the moment to work with the + # lambda system. not sure if this is the right approach. + # is there a valid use case to send a single non-expanding + # param to IN? check for ARRAY type? + element = element._clone(maintain_key=True) + element.expanding = True if isinstance(expr, elements.Tuple): element = element._with_expanding_in_types( [elem.type for elem in expr] @@ -431,6 +479,22 @@ class InElementImpl(RoleImpl): return element +class OnClauseImpl(_CoerceLiterals, _ColumnCoercions, RoleImpl): + __slots__ = () + + _coerce_consts = True + + def _post_coercion(self, resolved, original_element=None, **kw): + # this is a hack right now as we want to use coercion on an + # ORM InstrumentedAttribute, but we want to return the object + # itself if it is one, not its clause element. + # ORM context _join and _legacy_join() would need to be improved + # to look for annotations in a clause element form. + if isinstance(original_element, roles.JoinTargetRole): + return original_element + return resolved + + class WhereHavingImpl(_CoerceLiterals, _ColumnCoercions, RoleImpl): __slots__ = () @@ -635,6 +699,24 @@ class StatementImpl(_NoTextCoercion, RoleImpl): class CoerceTextStatementImpl(_CoerceLiterals, RoleImpl): __slots__ = () + def _literal_coercion(self, element, **kw): + if callable(element) and hasattr(element, "__code__"): + return lambdas.StatementLambdaElement(element, self._role_class) + else: + return super(CoerceTextStatementImpl, self)._literal_coercion( + element, **kw + ) + + def _implicit_coercions( + self, original_element, resolved, argname=None, **kw + ): + if resolved._is_lambda_element: + return resolved + else: + return super(CoerceTextStatementImpl, self)._implicit_coercions( + original_element, resolved, argname=argname, **kw + ) + def _text_coercion(self, element, argname=None): # TODO: this should emit deprecation warning, # see deprecation warning in engine/base.py execute() |