diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-02 17:35:43 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-12 10:29:30 -0500 |
commit | 43f6ae639ca0186f4802255861acdc20f19e702f (patch) | |
tree | 311d908ba5b72b0fcb751d682f56ccd73710d41b /lib/sqlalchemy/sql/default_comparator.py | |
parent | a869dc8fe3cd579ed9bab665d215a6c3e3d8a4ca (diff) | |
download | sqlalchemy-43f6ae639ca0186f4802255861acdc20f19e702f.tar.gz |
initial reorganize for static typing
start applying foundational annotations to key
elements.
two main elements addressed here:
1. removal of public_factory() and replacement with
explicit functions. this just works much better with
typing.
2. typing support for column expressions and operators.
The biggest part of this involves stubbing out all the
ColumnOperators methods under ColumnElement in a
TYPE_CHECKING section. Took me a while to see this
method vs. much more complicated things I thought
I needed.
Also for this version implementing #7519, ColumnElement
types against the Python type and not TypeEngine. it is
hoped this leads to easier transferrence between ORM/Core
as well as eventual support for result set typing.
Not clear yet how well this approach will work and what
new issues it may introduce.
given the current approach we now get full, rich typing for
scenarios like this:
from sqlalchemy import column, Integer, String, Boolean
c1 = column('a', String)
c2 = column('a', Integer)
expr1 = c2.in_([1, 2, 3])
expr2 = c2 / 5
expr3 = -c2
expr4_a = ~(c2 == 5)
expr4_b = ~column('q', Boolean)
expr5 = c1 + 'x'
expr6 = c2 + 10
Fixes: #7519
Fixes: #6810
Change-Id: I078d9f57955549f6f7868314287175f6c61c44cb
Diffstat (limited to 'lib/sqlalchemy/sql/default_comparator.py')
-rw-r--r-- | lib/sqlalchemy/sql/default_comparator.py | 279 |
1 files changed, 190 insertions, 89 deletions
diff --git a/lib/sqlalchemy/sql/default_comparator.py b/lib/sqlalchemy/sql/default_comparator.py index 55a586285..1759e686e 100644 --- a/lib/sqlalchemy/sql/default_comparator.py +++ b/lib/sqlalchemy/sql/default_comparator.py @@ -8,6 +8,15 @@ """Default implementation of SQL comparison operations. """ +import typing +from typing import Any +from typing import Callable +from typing import Dict +from typing import NoReturn +from typing import Optional +from typing import Tuple +from typing import Type +from typing import Union from . import coercions from . import operators @@ -16,28 +25,38 @@ from . import type_api from .elements import and_ from .elements import BinaryExpression from .elements import ClauseList -from .elements import collate +from .elements import CollationClause from .elements import CollectionAggregate from .elements import False_ from .elements import Null from .elements import or_ from .elements import True_ from .elements import UnaryExpression +from .operators import OperatorType from .. import exc from .. import util +_T = typing.TypeVar("_T", bound=Any) + +if typing.TYPE_CHECKING: + from .elements import ColumnElement + from .sqltypes import TypeEngine + def _boolean_compare( - expr, - op, - obj, - negate=None, - reverse=False, + expr: "ColumnElement", + op: OperatorType, + obj: roles.BinaryElementRole, + *, + negate_op: Optional[OperatorType] = None, + reverse: bool = False, _python_is_types=(util.NoneType, bool), _any_all_expr=False, - result_type=None, - **kwargs, -): + result_type: Optional[ + Union[Type["TypeEngine[bool]"], "TypeEngine[bool]"] + ] = None, + **kwargs: Any, +) -> BinaryExpression[bool]: if result_type is None: result_type = type_api.BOOLEANTYPE @@ -54,7 +73,7 @@ def _boolean_compare( coercions.expect(roles.ConstExprRole, obj), op, type_=result_type, - negate=negate, + negate=negate_op, modifiers=kwargs, ) elif op in ( @@ -66,7 +85,7 @@ def _boolean_compare( coercions.expect(roles.ConstExprRole, obj), op, type_=result_type, - negate=negate, + negate=negate_op, modifiers=kwargs, ) elif _any_all_expr: @@ -104,11 +123,21 @@ def _boolean_compare( if reverse: return BinaryExpression( - obj, expr, op, type_=result_type, negate=negate, modifiers=kwargs + obj, + expr, + op, + type_=result_type, + negate=negate_op, + modifiers=kwargs, ) else: return BinaryExpression( - expr, obj, op, type_=result_type, negate=negate, modifiers=kwargs + expr, + obj, + op, + type_=result_type, + negate=negate_op, + modifiers=kwargs, ) @@ -124,15 +153,26 @@ def _custom_op_operate(expr, op, obj, reverse=False, result_type=None, **kw): ) -def _binary_operate(expr, op, obj, reverse=False, result_type=None, **kw): - obj = coercions.expect( +def _binary_operate( + expr: "ColumnElement", + op: OperatorType, + obj: roles.BinaryElementRole, + *, + reverse=False, + result_type: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + **kw: Any, +) -> BinaryExpression[_T]: + + coerced_obj = coercions.expect( roles.BinaryElementRole, obj, expr=expr, operator=op ) if reverse: - left, right = obj, expr + left, right = coerced_obj, expr else: - left, right = expr, obj + left, right = expr, coerced_obj if result_type is None: op, result_type = left.comparator._adapt_expression( @@ -142,7 +182,7 @@ def _binary_operate(expr, op, obj, reverse=False, result_type=None, **kw): return BinaryExpression(left, right, op, type_=result_type, modifiers=kw) -def _conjunction_operate(expr, op, other, **kw): +def _conjunction_operate(expr, op, other, **kw) -> "ColumnElement": if op is operators.and_: return and_(expr, other) elif op is operators.or_: @@ -151,11 +191,11 @@ def _conjunction_operate(expr, op, other, **kw): raise NotImplementedError() -def _scalar(expr, op, fn, **kw): +def _scalar(expr, op, fn, **kw) -> "ColumnElement": return fn(expr) -def _in_impl(expr, op, seq_or_selectable, negate_op, **kw): +def _in_impl(expr, op, seq_or_selectable, negate_op, **kw) -> "ColumnElement": seq_or_selectable = coercions.expect( roles.InElementRole, seq_or_selectable, expr=expr, operator=op ) @@ -163,11 +203,11 @@ def _in_impl(expr, op, seq_or_selectable, negate_op, **kw): op, negate_op = seq_or_selectable._annotations["in_ops"] return _boolean_compare( - expr, op, seq_or_selectable, negate=negate_op, **kw + expr, op, seq_or_selectable, negate_op=negate_op, **kw ) -def _getitem_impl(expr, op, other, **kw): +def _getitem_impl(expr, op, other, **kw) -> "ColumnElement": if isinstance(expr.type, type_api.INDEXABLE): other = coercions.expect( roles.BinaryElementRole, other, expr=expr, operator=op @@ -177,13 +217,13 @@ def _getitem_impl(expr, op, other, **kw): _unsupported_impl(expr, op, other, **kw) -def _unsupported_impl(expr, op, *arg, **kw): +def _unsupported_impl(expr, op, *arg, **kw) -> NoReturn: raise NotImplementedError( "Operator '%s' is not supported on " "this expression" % op.__name__ ) -def _inv_impl(expr, op, **kw): +def _inv_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.__inv__`.""" # undocumented element currently used by the ORM for @@ -194,12 +234,12 @@ def _inv_impl(expr, op, **kw): return expr._negate() -def _neg_impl(expr, op, **kw): +def _neg_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.__neg__`.""" return UnaryExpression(expr, operator=operators.neg, type_=expr.type) -def _match_impl(expr, op, other, **kw): +def _match_impl(expr, op, other, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.match`.""" return _boolean_compare( @@ -212,21 +252,21 @@ def _match_impl(expr, op, other, **kw): operator=operators.match_op, ), result_type=type_api.MATCHTYPE, - negate=operators.not_match_op + negate_op=operators.not_match_op if op is operators.match_op else operators.match_op, **kw, ) -def _distinct_impl(expr, op, **kw): +def _distinct_impl(expr, op, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.distinct`.""" return UnaryExpression( expr, operator=operators.distinct_op, type_=expr.type ) -def _between_impl(expr, op, cleft, cright, **kw): +def _between_impl(expr, op, cleft, cright, **kw) -> "ColumnElement": """See :meth:`.ColumnOperators.between`.""" return BinaryExpression( expr, @@ -255,11 +295,11 @@ def _between_impl(expr, op, cleft, cright, **kw): ) -def _collate_impl(expr, op, other, **kw): - return collate(expr, other) +def _collate_impl(expr, op, collation, **kw) -> "ColumnElement": + return CollationClause._create_collation_expression(expr, collation) -def _regexp_match_impl(expr, op, pattern, flags, **kw): +def _regexp_match_impl(expr, op, pattern, flags, **kw) -> "ColumnElement": if flags is not None: flags = coercions.expect( roles.BinaryElementRole, @@ -272,14 +312,16 @@ def _regexp_match_impl(expr, op, pattern, flags, **kw): op, pattern, flags=flags, - negate=operators.not_regexp_match_op + negate_op=operators.not_regexp_match_op if op is operators.regexp_match_op else operators.regexp_match_op, **kw, ) -def _regexp_replace_impl(expr, op, pattern, replacement, flags, **kw): +def _regexp_replace_impl( + expr, op, pattern, replacement, flags, **kw +) -> "ColumnElement": replacement = coercions.expect( roles.BinaryElementRole, replacement, @@ -299,59 +341,118 @@ def _regexp_replace_impl(expr, op, pattern, replacement, flags, **kw): # a mapping of operators with the method they use, along with -# their negated operator for comparison operators -operator_lookup = { - "and_": (_conjunction_operate,), - "or_": (_conjunction_operate,), - "inv": (_inv_impl,), - "add": (_binary_operate,), - "mul": (_binary_operate,), - "sub": (_binary_operate,), - "div": (_binary_operate,), - "mod": (_binary_operate,), - "truediv": (_binary_operate,), - "floordiv": (_binary_operate,), - "custom_op": (_custom_op_operate,), - "json_path_getitem_op": (_binary_operate,), - "json_getitem_op": (_binary_operate,), - "concat_op": (_binary_operate,), - "any_op": (_scalar, CollectionAggregate._create_any), - "all_op": (_scalar, CollectionAggregate._create_all), - "lt": (_boolean_compare, operators.ge), - "le": (_boolean_compare, operators.gt), - "ne": (_boolean_compare, operators.eq), - "gt": (_boolean_compare, operators.le), - "ge": (_boolean_compare, operators.lt), - "eq": (_boolean_compare, operators.ne), - "is_distinct_from": (_boolean_compare, operators.is_not_distinct_from), - "is_not_distinct_from": (_boolean_compare, operators.is_distinct_from), - "like_op": (_boolean_compare, operators.not_like_op), - "ilike_op": (_boolean_compare, operators.not_ilike_op), - "not_like_op": (_boolean_compare, operators.like_op), - "not_ilike_op": (_boolean_compare, operators.ilike_op), - "contains_op": (_boolean_compare, operators.not_contains_op), - "startswith_op": (_boolean_compare, operators.not_startswith_op), - "endswith_op": (_boolean_compare, operators.not_endswith_op), - "desc_op": (_scalar, UnaryExpression._create_desc), - "asc_op": (_scalar, UnaryExpression._create_asc), - "nulls_first_op": (_scalar, UnaryExpression._create_nulls_first), - "nulls_last_op": (_scalar, UnaryExpression._create_nulls_last), - "in_op": (_in_impl, operators.not_in_op), - "not_in_op": (_in_impl, operators.in_op), - "is_": (_boolean_compare, operators.is_), - "is_not": (_boolean_compare, operators.is_not), - "collate": (_collate_impl,), - "match_op": (_match_impl,), - "not_match_op": (_match_impl,), - "distinct_op": (_distinct_impl,), - "between_op": (_between_impl,), - "not_between_op": (_between_impl,), - "neg": (_neg_impl,), - "getitem": (_getitem_impl,), - "lshift": (_unsupported_impl,), - "rshift": (_unsupported_impl,), - "contains": (_unsupported_impl,), - "regexp_match_op": (_regexp_match_impl,), - "not_regexp_match_op": (_regexp_match_impl,), - "regexp_replace_op": (_regexp_replace_impl,), +# additional keyword arguments to be passed +operator_lookup: Dict[ + str, Tuple[Callable[..., "ColumnElement"], util.immutabledict] +] = { + "and_": (_conjunction_operate, util.EMPTY_DICT), + "or_": (_conjunction_operate, util.EMPTY_DICT), + "inv": (_inv_impl, util.EMPTY_DICT), + "add": (_binary_operate, util.EMPTY_DICT), + "mul": (_binary_operate, util.EMPTY_DICT), + "sub": (_binary_operate, util.EMPTY_DICT), + "div": (_binary_operate, util.EMPTY_DICT), + "mod": (_binary_operate, util.EMPTY_DICT), + "truediv": (_binary_operate, util.EMPTY_DICT), + "floordiv": (_binary_operate, util.EMPTY_DICT), + "custom_op": (_custom_op_operate, util.EMPTY_DICT), + "json_path_getitem_op": (_binary_operate, util.EMPTY_DICT), + "json_getitem_op": (_binary_operate, util.EMPTY_DICT), + "concat_op": (_binary_operate, util.EMPTY_DICT), + "any_op": ( + _scalar, + util.immutabledict({"fn": CollectionAggregate._create_any}), + ), + "all_op": ( + _scalar, + util.immutabledict({"fn": CollectionAggregate._create_all}), + ), + "lt": (_boolean_compare, util.immutabledict({"negate_op": operators.ge})), + "le": (_boolean_compare, util.immutabledict({"negate_op": operators.gt})), + "ne": (_boolean_compare, util.immutabledict({"negate_op": operators.eq})), + "gt": (_boolean_compare, util.immutabledict({"negate_op": operators.le})), + "ge": (_boolean_compare, util.immutabledict({"negate_op": operators.lt})), + "eq": (_boolean_compare, util.immutabledict({"negate_op": operators.ne})), + "is_distinct_from": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_not_distinct_from}), + ), + "is_not_distinct_from": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_distinct_from}), + ), + "like_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_like_op}), + ), + "ilike_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_ilike_op}), + ), + "not_like_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.like_op}), + ), + "not_ilike_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.ilike_op}), + ), + "contains_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_contains_op}), + ), + "startswith_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_startswith_op}), + ), + "endswith_op": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.not_endswith_op}), + ), + "desc_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_desc}), + ), + "asc_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_asc}), + ), + "nulls_first_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_nulls_first}), + ), + "nulls_last_op": ( + _scalar, + util.immutabledict({"fn": UnaryExpression._create_nulls_last}), + ), + "in_op": ( + _in_impl, + util.immutabledict({"negate_op": operators.not_in_op}), + ), + "not_in_op": ( + _in_impl, + util.immutabledict({"negate_op": operators.in_op}), + ), + "is_": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_}), + ), + "is_not": ( + _boolean_compare, + util.immutabledict({"negate_op": operators.is_not}), + ), + "collate": (_collate_impl, util.EMPTY_DICT), + "match_op": (_match_impl, util.EMPTY_DICT), + "not_match_op": (_match_impl, util.EMPTY_DICT), + "distinct_op": (_distinct_impl, util.EMPTY_DICT), + "between_op": (_between_impl, util.EMPTY_DICT), + "not_between_op": (_between_impl, util.EMPTY_DICT), + "neg": (_neg_impl, util.EMPTY_DICT), + "getitem": (_getitem_impl, util.EMPTY_DICT), + "lshift": (_unsupported_impl, util.EMPTY_DICT), + "rshift": (_unsupported_impl, util.EMPTY_DICT), + "contains": (_unsupported_impl, util.EMPTY_DICT), + "regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT), + "not_regexp_match_op": (_regexp_match_impl, util.EMPTY_DICT), + "regexp_replace_op": (_regexp_replace_impl, util.EMPTY_DICT), } |