Fixed typing issue where :class:`.coalesce` would not return the correct
return type when a nullable form of that argument were passed, even though
this function is meant to select the non-null entry among possibly null
arguments. Pull request courtesy Yannick PÉROUX.
Closes: #12963
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12963
Pull-request-sha:
05d0d9784d4497fb3bfee540fbc51747c1767c90
Change-Id: Ife83a384ea57faf446c1fdb542df14627348f40f
--- /dev/null
+.. change::
+ :tags: bug, typing
+
+ Fixed typing issue where :class:`.coalesce` would not return the correct
+ return type when a nullable form of that argument were passed, even though
+ this function is meant to select the non-null entry among possibly null
+ arguments. Pull request courtesy Yannick PÉROUX.
+
@overload
def coalesce(
self,
- col: _ColumnExpressionArgument[_T],
+ col: _ColumnExpressionArgument[Optional[_T]],
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> coalesce[_T]: ...
@overload
def coalesce(
self,
- col: _T,
+ col: Optional[_T],
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> coalesce[_T]: ...
def coalesce(
self,
- col: _ColumnExpressionOrLiteralArgument[_T],
+ col: _ColumnExpressionOrLiteralArgument[Optional[_T]],
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> coalesce[_T]: ...
super().__init__(*fn_args, **kwargs)
-class coalesce(ReturnTypeFromArgs[_T]):
+class ReturnTypeFromOptionalArgs(ReturnTypeFromArgs[_T]):
+ inherit_cache = True
+
+ @overload
+ def __init__(
+ self,
+ col: ColumnElement[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> None: ...
+
+ @overload
+ def __init__(
+ self,
+ col: _ColumnExpressionArgument[Optional[_T]],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> None: ...
+
+ @overload
+ def __init__(
+ self,
+ col: Optional[_T],
+ *args: _ColumnExpressionOrLiteralArgument[Any],
+ **kwargs: Any,
+ ) -> None: ...
+
+ def __init__(
+ self,
+ *args: _ColumnExpressionOrLiteralArgument[Optional[_T]],
+ **kwargs: Any,
+ ) -> None:
+ super().__init__(*args, **kwargs) # type: ignore
+
+
+class coalesce(ReturnTypeFromOptionalArgs[_T]):
_has_args = True
inherit_cache = True
from sqlalchemy import Integer
from sqlalchemy import Select
from sqlalchemy import select
+from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
a: Mapped[int]
b: Mapped[int]
c: Mapped[str]
+ _d: Mapped[int | None] = mapped_column("d")
+
+ @hybrid_property
+ def d(self) -> int | None:
+ return self._d
assert_type(
assert_type(func.coalesce("a", "b"), coalesce[str])
assert_type(func.coalesce(column("x", Integer), 3), coalesce[int])
+assert_type(func.coalesce(Foo._d, 100), coalesce[int])
stmt2 = select(Foo.a, func.coalesce(Foo.c, "a", "b")).group_by(Foo.a)
assert_type(stmt2, Select[int, str])
from sqlalchemy.sql.functions import _registry
from sqlalchemy.sql.functions import ReturnTypeFromArgs
+from sqlalchemy.sql.functions import ReturnTypeFromOptionalArgs
from sqlalchemy.types import TypeEngine
from sqlalchemy.util.tool_support import code_writer_cmd
reg = _registry["_default"]
for key in sorted(reg):
cls = reg[key]
- if cls is ReturnTypeFromArgs:
+ if cls is ReturnTypeFromArgs or cls is ReturnTypeFromOptionalArgs:
continue
yield key, cls
is_reserved_word = key in builtins
if issubclass(fn_class, ReturnTypeFromArgs):
+ if issubclass(fn_class, ReturnTypeFromOptionalArgs):
+ _TEE = "Optional[_T]"
+ else:
+ _TEE = "_T"
+
buf.write(
textwrap.indent(
f"""
@overload
def {key}( {' # noqa: A001' if is_reserved_word else ''}
self,
- col: _ColumnExpressionArgument[_T],
+ col: _ColumnExpressionArgument[{_TEE}],
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> {fn_class.__name__}[_T]:
@overload
def {key}( {' # noqa: A001' if is_reserved_word else ''}
self,
- col: _T,
+ col: {_TEE},
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> {fn_class.__name__}[_T]:
def {key}( {' # noqa: A001' if is_reserved_word else ''}
self,
- col: _ColumnExpressionOrLiteralArgument[_T],
+ col: _ColumnExpressionOrLiteralArgument[{_TEE}],
*args: _ColumnExpressionOrLiteralArgument[Any],
**kwargs: Any,
) -> {fn_class.__name__}[_T]: