--- /dev/null
+.. change::
+ :tags: bug, typing
+ :tickets: 13091
+
+ Fixed issue in new :pep:`646` support for result sets where an issue in the
+ mypy type checker prevented "scalar" methods including
+ :meth:`.Connection.scalar`, :meth:`.Result.scalar`,
+ :meth:`_orm.Session.scalar`, as well as async versions of these methods
+ from applying the correct type to the scalar result value, when the columns
+ in the originating :func:`_sql.select` were typed as ``Any``. Pull request
+ courtesy Yurii Karabas.
+
from .. import util
from ..sql import compiler
from ..sql import util as sql_util
+from ..util.typing import Never
from ..util.typing import TupleAny
from ..util.typing import TypeVarTuple
from ..util.typing import Unpack
self._dbapi_connection = None
self.__can_reconnect = False
+ # special case to handle mypy issue:
+ # https://github.com/python/mypy/issues/20651
+ @overload
+ def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ parameters: Optional[_CoreSingleExecuteParams] = None,
+ *,
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
+ ) -> Optional[Any]: ...
+
@overload
def scalar(
self,
from ..sql.base import InPlaceGenerative
from ..util import deprecated
from ..util import NONE_SET
+from ..util.typing import Never
from ..util.typing import Self
from ..util.typing import TupleAny
from ..util.typing import TypeVarTuple
raise_for_second_row=True, raise_for_none=True, scalar=False
)
+ # special case to handle mypy issue:
+ # https://github.com/python/mypy/issues/20651
+ @overload
+ def scalar(self: Result[Never, Unpack[TupleAny]]) -> Optional[Any]:
+ pass
+
+ @overload
+ def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
+ pass
+
def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
"""Fetch the first column of the first row, and close the result set.
from ...exc import ArgumentError
from ...util import immutabledict
from ...util.concurrency import greenlet_spawn
+from ...util.typing import Never
from ...util.typing import TupleAny
from ...util.typing import TypeVarTuple
from ...util.typing import Unpack
)
return await _ensure_sync_result(result, self.execute)
+ # special case to handle mypy issue:
+ # https://github.com/python/mypy/issues/20651
+ @overload
+ async def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ parameters: Optional[_CoreSingleExecuteParams] = None,
+ *,
+ execution_options: Optional[CoreExecuteOptionsParameter] = None,
+ ) -> Optional[Any]: ...
+
@overload
async def scalar(
self,
from ...util import ScopedRegistry
from ...util import warn
from ...util import warn_deprecated
+from ...util.typing import Never
from ...util.typing import TupleAny
from ...util.typing import TypeVarTuple
from ...util.typing import Unpack
return await self._proxied.rollback()
+ @overload
+ async def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ params: Optional[_CoreAnyExecuteParams] = None,
+ *,
+ execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
+ bind_arguments: Optional[_BindArguments] = None,
+ **kw: Any,
+ ) -> Optional[Any]: ...
+
@overload
async def scalar(
self,
from ...orm import SessionTransaction
from ...orm import state as _instance_state
from ...util.concurrency import greenlet_spawn
+from ...util.typing import Never
from ...util.typing import TupleAny
from ...util.typing import TypeVarTuple
from ...util.typing import Unpack
)
return await _ensure_sync_result(result, self.execute)
+ # special case to handle mypy issue:
+ # https://github.com/python/mypy/issues/20651
+ @overload
+ async def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ params: Optional[_CoreAnyExecuteParams] = None,
+ *,
+ execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
+ bind_arguments: Optional[_BindArguments] = None,
+ **kw: Any,
+ ) -> Optional[Any]: ...
+
@overload
async def scalar(
self,
from ..util import ThreadLocalRegistry
from ..util import warn
from ..util import warn_deprecated
+from ..util.typing import Never
from ..util.typing import TupleAny
from ..util.typing import TypeVarTuple
from ..util.typing import Unpack
return self._proxied.rollback()
+ @overload
+ def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ params: Optional[_CoreSingleExecuteParams] = None,
+ *,
+ execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
+ bind_arguments: Optional[_BindArguments] = None,
+ **kw: Any,
+ ) -> Optional[Any]: ...
+
@overload
def scalar(
self,
from ..sql.selectable import ForUpdateArg
from ..util import deprecated_params
from ..util import IdentitySet
+from ..util.typing import Never
from ..util.typing import TupleAny
from ..util.typing import TypeVarTuple
from ..util.typing import Unpack
_add_event=_add_event,
)
+ # special case to handle mypy issue:
+ # https://github.com/python/mypy/issues/20651
+ @overload
+ def scalar(
+ self,
+ statement: TypedReturnsRows[Never],
+ params: Optional[_CoreSingleExecuteParams] = None,
+ *,
+ execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
+ bind_arguments: Optional[_BindArguments] = None,
+ **kw: Any,
+ ) -> Optional[Any]: ...
+
@overload
def scalar(
self,
from __future__ import annotations
import asyncio
+from typing import Any
from typing import assert_type
from typing import List
+from typing import Tuple
+from typing import Unpack
+from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
+from sqlalchemy import Integer
+from sqlalchemy import MetaData
+from sqlalchemy import Result
+from sqlalchemy import Select
+from sqlalchemy import select
+from sqlalchemy import String
+from sqlalchemy import Table
from sqlalchemy.engine.row import Row
from sqlalchemy.ext.asyncio import async_scoped_session
from sqlalchemy.ext.asyncio import async_sessionmaker
user: Mapped[User] = relationship(back_populates="addresses")
+user_table = Table(
+ "user",
+ MetaData(),
+ Column("id", Integer, primary_key=True),
+ Column("name", String, primary_key=True),
+)
+
+
e = create_engine("sqlite://")
Base.metadata.create_all(e)
await scoped.connection(
execution_options={"isolation_level": "REPEATABLE READ"}
)
+
+
+def test_13091() -> None:
+ session = Session()
+ stmt = select(user_table.c.id)
+ assert_type(stmt, Select[Unpack[Tuple[Any, ...]]])
+ result = session.execute(stmt)
+
+ assert_type(result, Result[Unpack[Tuple[Any, ...]]])
+ data1 = result.scalar()
+ assert_type(data1, Any | None)
+
+ data2 = session.scalar(stmt)
+ assert_type(data2, Any | None)
+
+
+async def async_test_13091() -> None:
+ session = AsyncSession()
+ stmt = select(user_table.c.id)
+ assert_type(stmt, Select[Unpack[Tuple[Any, ...]]])
+ result = await session.execute(stmt)
+
+ assert_type(result, Result[Unpack[Tuple[Any, ...]]])
+ data1 = result.scalar()
+ assert_type(data1, Any | None)
+
+ data2 = await session.scalar(stmt)
+ assert_type(data2, Any | None)
from typing import cast
from typing import Optional
from typing import Sequence
+from typing import Tuple
from typing import Type
from typing import Unpack
from sqlalchemy import Column
from sqlalchemy import column
+from sqlalchemy import Connection
from sqlalchemy import create_engine
from sqlalchemy import func
from sqlalchemy import insert
print(stmt4)
print(stmt, stmt2, stmt3)
+
+
+def test_13091() -> None:
+ with e.connect() as conn:
+ stmt = select(t_user.c.id)
+ assert_type(stmt, Select[Unpack[Tuple[Any, ...]]])
+ result = conn.execute(stmt)
+
+ assert_type(result, CursorResult[Unpack[Tuple[Any, ...]]])
+ data1 = result.scalar()
+ assert_type(data1, Any | None)
+
+ data2 = conn.scalar(stmt)
+ assert_type(data2, Any | None)
+
+
+async def async_test_13091() -> None:
+ async with ae.connect() as conn:
+ stmt = select(t_user.c.id)
+ assert_type(stmt, Select[Unpack[Tuple[Any, ...]]])
+ result = await conn.execute(stmt)
+
+ assert_type(result, CursorResult[Unpack[Tuple[Any, ...]]])
+ data1 = result.scalar()
+ assert_type(data1, Any | None)
+
+ data2 = await conn.scalar(stmt)
+ assert_type(data2, Any | None)
+
+
+def test_13091_2(
+ conn: Connection, table: Table, c: Column[int], c2: Column[str]
+) -> None:
+ assert_type(table.select(), Select[Unpack[Tuple[Any, ...]]])
+ r1 = conn.execute(table.select())
+ assert_type(r1, CursorResult[Unpack[Tuple[Any, ...]]])
+ d1 = r1.scalar()
+ assert_type(d1, Any | None)
+ r2 = conn.execute(select(table))
+ assert_type(r2, CursorResult[Unpack[Tuple[Any, ...]]])
+ d2 = r2.scalar()
+ assert_type(d2, Any | None)
+ r3 = conn.execute(select(c))
+ assert_type(r3, CursorResult[int])
+ d3 = r3.scalar()
+ assert_type(d3, int | None)
+ r4 = conn.execute(select(c, c2))
+ assert_type(r4, CursorResult[int, str])
+ d4 = r3.scalar()
+ assert_type(d4, int | None)