--- /dev/null
+.. change::
+ :tags: typing, bug
+ :tickets: 9122
+
+ The :meth:`_sql.ColumnOperators.in_` and
+ :meth:`_sql.ColumnOperators.not_in_` are typed to include
+ ``Iterable[Any]`` rather than ``Sequence[Any]`` for more flexibility in
+ argument type.
+
+
+.. change::
+ :tags: typing, bug
+ :tickets: 9123
+
+ The :func:`_sql.or_` and :func:`_sql.and_` from a typing perspective
+ require the first argument to be present, however these functions still
+ accept zero arguments which will emit a deprecation warning at runtime.
+ Typing is also added to support sending the fixed literal ``False`` for
+ :func:`_sql.or_` and ``True`` for :func:`_sql.and_` as the first argument
+ only, however the documentation now indicates sending the
+ :func:`_sql.false` and :func:`_sql.true` constructs in these cases as a
+ more explicit approach.
+
+
+.. change::
+ :tags: typing, bug
+ :tickets: 9125
+
+ Fixed typing issue where iterating over a :class:`_orm.Query` object
+ was not correctly typed.
from typing import Dict
from typing import Generic
from typing import Iterable
+from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
except sa_exc.NoResultFound:
return None
- def __iter__(self) -> Iterable[_T]:
+ def __iter__(self) -> Iterator[_T]:
result = self._iter()
try:
- yield from result
+ yield from result # type: ignore
except GeneratorExit:
# issue #8710 - direct iteration is not re-usable after
# an iterable block is broken, so close the result
from typing import overload
from typing import Sequence
from typing import Tuple as typing_Tuple
+from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
return CollectionAggregate._create_all(expr)
-def and_(*clauses: _ColumnExpressionArgument[bool]) -> ColumnElement[bool]:
+def and_( # type: ignore[empty-body]
+ initial_clause: Union[Literal[True], _ColumnExpressionArgument[bool]],
+ *clauses: _ColumnExpressionArgument[bool],
+) -> ColumnElement[bool]:
r"""Produce a conjunction of expressions joined by ``AND``.
E.g.::
argument in order to be valid; a :func:`.and_` construct with no
arguments is ambiguous. To produce an "empty" or dynamically
generated :func:`.and_` expression, from a given list of expressions,
- a "default" element of ``True`` should be specified::
+ a "default" element of :func:`_sql.true` (or just ``True``) should be
+ specified::
- criteria = and_(True, *expressions)
+ from sqlalchemy import true
+ criteria = and_(true(), *expressions)
The above expression will compile to SQL as the expression ``true``
or ``1 = 1``, depending on backend, if no other expressions are
- present. If expressions are present, then the ``True`` value is
+ present. If expressions are present, then the :func:`_sql.true` value is
ignored as it does not affect the outcome of an AND expression that
has other elements.
:func:`.or_`
"""
- return BooleanClauseList.and_(*clauses)
+ ...
+
+
+if not TYPE_CHECKING:
+ # handle deprecated case which allows zero-arguments
+ def and_(*clauses): # noqa: F811
+ return BooleanClauseList.and_(*clauses)
def any_(expr: _ColumnExpressionArgument[_T]) -> CollectionAggregate[bool]:
return UnaryExpression._create_nulls_last(column)
-def or_(*clauses: _ColumnExpressionArgument[bool]) -> ColumnElement[bool]:
+def or_( # type: ignore[empty-body]
+ initial_clause: Union[Literal[False], _ColumnExpressionArgument[bool]],
+ *clauses: _ColumnExpressionArgument[bool],
+) -> ColumnElement[bool]:
"""Produce a conjunction of expressions joined by ``OR``.
E.g.::
argument in order to be valid; a :func:`.or_` construct with no
arguments is ambiguous. To produce an "empty" or dynamically
generated :func:`.or_` expression, from a given list of expressions,
- a "default" element of ``False`` should be specified::
+ a "default" element of :func:`_sql.false` (or just ``False``) should be
+ specified::
- or_criteria = or_(False, *expressions)
+ from sqlalchemy import false
+ or_criteria = or_(false(), *expressions)
The above expression will compile to SQL as the expression ``false``
or ``0 = 1``, depending on backend, if no other expressions are
- present. If expressions are present, then the ``False`` value is
+ present. If expressions are present, then the :func:`_sql.false` value is
ignored as it does not affect the outcome of an OR expression which
has other elements.
:func:`.and_`
"""
- return BooleanClauseList.or_(*clauses)
+ ...
+
+
+if not TYPE_CHECKING:
+ # handle deprecated case which allows zero-arguments
+ def or_(*clauses): # noqa: F811
+ return BooleanClauseList.or_(*clauses)
def over(
@overload
def expect(
- role: Union[Type[roles.JoinTargetRole], Type[roles.OnClauseRole]],
+ role: Type[roles.JoinTargetRole],
element: _JoinTargetProtocol,
**kw: Any,
) -> _JoinTargetProtocol:
def in_(
self,
other: Union[
- Sequence[Any], BindParameter[Any], roles.InElementRole
+ Iterable[Any], BindParameter[Any], roles.InElementRole
],
) -> BinaryExpression[bool]:
...
def not_in(
self,
other: Union[
- Sequence[Any], BindParameter[Any], roles.InElementRole
+ Iterable[Any], BindParameter[Any], roles.InElementRole
],
) -> BinaryExpression[bool]:
...
operator: OperatorType,
continue_on: Any,
skip_on: Any,
- *clauses: _ColumnExpressionArgument[Any],
+ initial_clause: Any = _NoArg.NO_ARG,
+ *clauses: Any,
**kw: Any,
) -> ColumnElement[Any]:
+
+ if initial_clause is _NoArg.NO_ARG:
+ # no elements period. deprecated use case. return an empty
+ # ClauseList construct that generates nothing unless it has
+ # elements added to it.
+ name = operator.__name__
+
+ util.warn_deprecated(
+ f"Invoking {name}() without arguments is deprecated, and "
+ f"will be disallowed in a future release. For an empty "
+ f"""{name}() construct, use '{name}({
+ 'true()' if continue_on is True_._singleton else 'false()'
+ }, *args)' """
+ f"""or '{name}({
+ 'True' if continue_on is True_._singleton else 'False'
+ }, *args)'.""",
+ version="1.4",
+ )
+ return cls._construct_raw(operator) # type: ignore[no-any-return]
+
lcc, convert_clauses = cls._process_clauses_for_boolean(
operator,
continue_on,
skip_on,
[
coercions.expect(roles.WhereHavingRole, clause)
- for clause in util.coerce_generator_arg(clauses)
+ for clause in util.coerce_generator_arg(
+ (initial_clause,) + clauses
+ )
],
)
)
return cls._construct_raw(operator, flattened_clauses) # type: ignore # noqa: E501
- elif lcc == 1:
+ else:
+ assert lcc
# just one element. return it as a single boolean element,
# not a list and discard the operator.
return convert_clauses[0] # type: ignore[no-any-return] # noqa: E501
- else:
- # no elements period. deprecated use case. return an empty
- # ClauseList construct that generates nothing unless it has
- # elements added to it.
- util.warn_deprecated(
- "Invoking %(name)s() without arguments is deprecated, and "
- "will be disallowed in a future release. For an empty "
- "%(name)s() construct, use %(name)s(%(continue_on)s, *args)."
- % {
- "name": operator.__name__,
- "continue_on": "True"
- if continue_on is True_._singleton
- else "False",
- },
- version="1.4",
- )
- return cls._construct_raw(operator) # type: ignore[no-any-return] # noqa: E501
@classmethod
def _construct_for_whereclause(
@classmethod
def and_(
- cls, *clauses: _ColumnExpressionArgument[bool]
+ cls,
+ initial_clause: Union[
+ Literal[True], _ColumnExpressionArgument[bool], _NoArg
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionArgument[bool],
) -> ColumnElement[bool]:
r"""Produce a conjunction of expressions joined by ``AND``.
See :func:`_sql.and_` for full documentation.
"""
return cls._construct(
- operators.and_, True_._singleton, False_._singleton, *clauses
+ operators.and_,
+ True_._singleton,
+ False_._singleton,
+ initial_clause,
+ *clauses,
)
@classmethod
def or_(
- cls, *clauses: _ColumnExpressionArgument[bool]
+ cls,
+ initial_clause: Union[
+ Literal[False], _ColumnExpressionArgument[bool], _NoArg
+ ] = _NoArg.NO_ARG,
+ *clauses: _ColumnExpressionArgument[bool],
) -> ColumnElement[bool]:
"""Produce a conjunction of expressions joined by ``OR``.
See :func:`_sql.or_` for full documentation.
"""
return cls._construct(
- operators.or_, False_._singleton, True_._singleton, *clauses
+ operators.or_,
+ False_._singleton,
+ True_._singleton,
+ initial_clause,
+ *clauses,
)
@property
)
sess.query(User).update({"name": User.name + " some name"})
+ # test #9125
+
+ for row in sess.query(User.id, User.name):
+
+ # EXPECTED_TYPE: Row[Tuple[int, str]]
+ reveal_type(row)
+
+ for uobj1 in sess.query(User):
+
+ # EXPECTED_TYPE: User
+ reveal_type(uobj1)
+
# more result tests in typed_results.py
import typing
+from sqlalchemy import and_
from sqlalchemy import Boolean
from sqlalchemy import column
+from sqlalchemy import false
from sqlalchemy import func
from sqlalchemy import Integer
+from sqlalchemy import or_
from sqlalchemy import select
from sqlalchemy import String
+from sqlalchemy import true
# builtin.pyi stubs define object.__eq__() as returning bool, which
expr2 = c2.in_([1, 2, 3])
+expr2_set = c2.in_({1, 2, 3})
+
+expr2_gen = c2.in_((x for x in (1, 2, 3)))
+
+nexpr2 = c2.not_in([1, 2, 3])
+
+nexpr2_set = c2.not_in({1, 2, 3})
+
+nexpr2_gen = c2.not_in((x for x in (1, 2, 3)))
+
+short_cir1 = and_(True, c2 == 5)
+short_cir2 = or_(False, c2 == 5)
+
+short_cir3 = and_(true(), c2 == 5)
+short_cir4 = or_(false(), c2 == 5)
+
+# EXPECTED_MYPY: Missing positional argument "initial_clause" in call to "and_"
+no_empty_1 = and_()
+
+# EXPECTED_MYPY: Missing positional argument "initial_clause" in call to "or_"
+no_empty_2 = or_()
+
expr3 = c2 / 5
expr4 = -c2
with testing.expect_deprecated(
r"Invoking and_\(\) without arguments is deprecated, and "
r"will be disallowed in a future release. For an empty "
- r"and_\(\) construct, use and_\(True, \*args\)"
+ r"and_\(\) construct, use 'and_\(true\(\), \*args\)' or "
+ r"'and_\(True, \*args\)'"
):
self.assert_compile(or_(and_()), "")
import datetime
import operator
import pickle
+import re
from sqlalchemy import and_
from sqlalchemy import between
dialect=default.DefaultDialect(supports_native_boolean=False),
)
- @combinations((and_, "and_", "True"), (or_, "or_", "False"))
- def test_empty_clauses(self, op, str_op, str_continue):
+ @combinations(
+ (and_, "and_", r"true", "True"),
+ (or_, "or_", r"false", "False"),
+ )
+ def test_empty_clauses(self, op, str_op, str_continue, str_continue_2):
# these warning classes will change to ArgumentError when the
# deprecated behavior is disabled
with expect_deprecated(
- r"Invoking %(str_op)s\(\) without arguments is deprecated, and "
- r"will be disallowed in a future release. For an empty "
- r"%(str_op)s\(\) construct, use "
- r"%(str_op)s\(%(str_continue)s, \*args\)\."
- % {"str_op": str_op, "str_continue": str_continue}
+ re.escape(
+ f"Invoking {str_op}() without arguments is deprecated, and "
+ "will be disallowed in a future release. For an empty "
+ f"{str_op}() construct, use "
+ f"'{str_op}({str_continue}(), *args)' or "
+ f"'{str_op}({str_continue_2}, *args)'."
+ )
):
op()
+ def test_empty_construct_for_whereclause(self):
+ eq_(BooleanClauseList._construct_for_whereclause(()), None)
+
+ def test_non_empty_construct_for_whereclause(self):
+ self.assert_compile(
+ BooleanClauseList._construct_for_whereclause([column("q") == 5]),
+ "q = :q_1",
+ )
+
def test_empty_and_raw(self):
self.assert_compile(
BooleanClauseList._construct_raw(operators.and_), ""