]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add PEP 646 integration
authorYurii Karabas <1998uriyyo@gmail.com>
Wed, 17 Jan 2024 17:08:05 +0000 (12:08 -0500)
committerFederico Caselli <cfederico87@gmail.com>
Mon, 22 Jan 2024 18:49:05 +0000 (19:49 +0100)
The :class:`.Row` object now no longer makes use of an intermediary
``Tuple`` in order to represent its individual element types; instead,
the individual element types are present directly, via new :pep:`646`
integration, now available in more recent versions of Mypy.  Mypy
1.7 or greater is now required for statements, results and rows
to be correctly typed.   Pull request courtesy Yurii Karabas.

Fixes: #10635
Closes: #10634
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/10634
Pull-request-sha: 430785c8a04a48fe96ce35b4f4e08476700c1f79

Change-Id: Ibd0ae31a98b4ea69dcb89f970e640920b2be6c48

58 files changed:
doc/build/changelog/migration_21.rst
doc/build/changelog/unreleased_21/10296.rst
doc/build/changelog/unreleased_21/10635.rst [new file with mode: 0644]
doc/build/changelog/whatsnew_20.rst
lib/sqlalchemy/engine/_py_row.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/engine/cursor.py
lib/sqlalchemy/engine/default.py
lib/sqlalchemy/engine/events.py
lib/sqlalchemy/engine/result.py
lib/sqlalchemy/engine/row.py
lib/sqlalchemy/ext/asyncio/engine.py
lib/sqlalchemy/ext/asyncio/result.py
lib/sqlalchemy/ext/asyncio/scoping.py
lib/sqlalchemy/ext/asyncio/session.py
lib/sqlalchemy/ext/horizontal_shard.py
lib/sqlalchemy/orm/bulk_persistence.py
lib/sqlalchemy/orm/context.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/scoping.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/orm/writeonly.py
lib/sqlalchemy/sql/_selectable_constructors.py
lib/sqlalchemy/sql/_typing.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/dml.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/functions.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/sql/util.py
lib/sqlalchemy/util/typing.py
test/base/test_result.py
test/orm/test_query.py
test/sql/test_resultset.py
test/typing/plain_files/engine/engines.py
test/typing/plain_files/ext/asyncio/engines.py
test/typing/plain_files/ext/hybrid/hybrid_one.py
test/typing/plain_files/orm/composite.py
test/typing/plain_files/orm/composite_dc.py
test/typing/plain_files/orm/declared_attr_one.py
test/typing/plain_files/orm/issue_9340.py
test/typing/plain_files/orm/session.py
test/typing/plain_files/orm/typed_queries.py
test/typing/plain_files/sql/common_sql_element.py
test/typing/plain_files/sql/functions.py
test/typing/plain_files/sql/functions_again.py
test/typing/plain_files/sql/lambda_stmt.py
test/typing/plain_files/sql/typed_results.py
tools/generate_sql_functions.py
tools/generate_tuple_map_overloads.py
tox.ini

index 8edea83839908800aa181ab568a7421afc1c6610..95112b09b72844ea0475677bcc19e6d9ce47cb3e 100644 (file)
@@ -10,6 +10,76 @@ What's New in SQLAlchemy 2.1?
     version 2.1.
 
 
+.. _change_10635:
+
+``Row`` now represents individual column types directly without ``Tuple``
+--------------------------------------------------------------------------
+
+SQLAlchemy 2.0 implemented a broad array of :pep:`484` typing throughout
+all components, including a new ability for row-returning statements such
+as :func:`_sql.select` to maintain track of individual column types, which
+were then passed through the execution phase onto the :class:`_engine.Result`
+object and then to the individual :class:`_engine.Row` objects.   Described
+at :ref:`change_result_typing_20`, this approach solved several issues
+with statement / row typing, but some remained unsolvable.  In 2.1, one
+of those issues, that the individual column types needed to be packaged
+into a ``typing.Tuple``, is now resolved using new :pep:`646` integration,
+which allows for tuple-like types that are not actually typed as ``Tuple``.
+
+In SQLAlchemy 2.0, a statement such as::
+
+    stmt = select(column("x", Integer), column("y", String))
+
+Would be typed as::
+
+    Select[Tuple[int, str]]
+
+In 2.1, it's now typed as::
+
+    Select[int, str]
+
+When executing ``stmt``, the :class:`_engine.Result` and :class:`_engine.Row`
+objects will be typed as ``Result[int, str]`` and ``Row[int, str]``, respectively.
+The prior workaround using :attr:`_engine.Row._t` to type as a real ``Tuple``
+is no longer needed and projects can migrate off this pattern.
+
+Mypy users will need to make use of **Mypy 1.7 or greater** for pep-646
+integration to be available.
+
+Limitations
+^^^^^^^^^^^
+
+Not yet solved by pep-646 or any other pep is the ability for an arbitrary
+number of expressions within :class:`_sql.Select` and others to be mapped to
+row objects, without stating each argument position explicitly within typing
+annotations.   To work around this issue, SQLAlchemy makes use of automated
+"stub generation" tools to generate hardcoded mappings of different numbers of
+positional arguments to constructs like :func:`_sql.select` to resolve to
+individual ``Unpack[]`` expressions (in SQLAlchemy 2.0, this generation
+prodcued ``Tuple[]`` annotations instead).  This means that there are arbitrary
+limits on how many specific column expressions will be typed within the
+:class:`_engine.Row` object, without restoring to ``Any`` for remaining
+expressions; for :func:`_sql.select`, it's currently ten expressions, and
+for DML expresions like :func:`_dml.insert` that use :meth:`_dml.Insert.returning`,
+it's eight.    If and when a new pep that provides a ``Map`` operator
+to pep-646 is proposed, this limitation can be lifted. [1]_  Originally, it was
+mistakenly assumed that this limitation prevented pep-646 from being usable at all,
+however, the ``Unpack`` construct does in fact replace everything that
+was done using ``Tuple`` in 2.0.
+
+An additional limitation for which there is no proposed solution is that
+there's no way for the name-based attributes on :class:`_engine.Row` to be
+automatically typed, so these continue to be typed as ``Any`` (e.g. ``row.x``
+and ``row.y`` for the above example).   With current language features,
+this could only be fixed by having an explicit class-based construct that
+allows one to compose an explicit :class:`_engine.Row` with explicit fields
+up front, which would be verbose and not automatic.
+
+.. [1] https://github.com/python/typing/discussions/1001#discussioncomment-1897813
+
+:ticket:`10635`
+
+
 .. _change_10197:
 
 Asyncio "greenlet" dependency no longer installs by default
index c674ecbe1aea7a206150421f21ae113b12a96407..c58eb856602cc25695f7afa87f47319bf8341979 100644 (file)
@@ -7,4 +7,4 @@
     be imported only when the asyncio extension is first imported.
     Alternatively, the ``greenlet`` library is still imported lazily on
     first use to support use case that don't make direct use of the
-    SQLAlchemy asyncio extension.
\ No newline at end of file
+    SQLAlchemy asyncio extension.
diff --git a/doc/build/changelog/unreleased_21/10635.rst b/doc/build/changelog/unreleased_21/10635.rst
new file mode 100644 (file)
index 0000000..81fbba9
--- /dev/null
@@ -0,0 +1,14 @@
+.. change::
+    :tags: typing, feature
+    :tickets: 10635
+
+    The :class:`.Row` object now no longer makes use of an intermediary
+    ``Tuple`` in order to represent its individual element types; instead,
+    the individual element types are present directly, via new :pep:`646`
+    integration, now available in more recent versions of Mypy.  Mypy
+    1.7 or greater is now required for statements, results and rows
+    to be correctly typed.   Pull request courtesy Yurii Karabas.
+
+    .. seealso::
+
+        :ref:`change_10635`
index 179ed55f2dace0626f7c2bbaabc59b99b5827d8d..66610e26c4eb68f464a6322fa1d229f07c8c8f12 100644 (file)
@@ -75,6 +75,7 @@ result set.
    for the 2.0 series. Typing details are subject to change however
    significant backwards-incompatible changes are not planned.
 
+.. _change_result_typing_20:
 
 SQL Expression / Statement / Result Set Typing
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index 4e1dd7d430d7d96ce47c03e465bffde64449d20c..94ba85f2c267f10816bc429376bcaa6166d20108 100644 (file)
@@ -18,10 +18,11 @@ from typing import Optional
 from typing import Tuple
 from typing import Type
 
+from ..util.typing import TupleAny
+
 if typing.TYPE_CHECKING:
     from .result import _KeyType
     from .result import _ProcessorsType
-    from .result import _RawRowType
     from .result import _TupleGetterType
     from .result import ResultMetaData
 
@@ -33,14 +34,14 @@ class BaseRow:
 
     _parent: ResultMetaData
     _key_to_index: Mapping[_KeyType, int]
-    _data: _RawRowType
+    _data: TupleAny
 
     def __init__(
         self,
         parent: ResultMetaData,
         processors: Optional[_ProcessorsType],
         key_to_index: Mapping[_KeyType, int],
-        data: _RawRowType,
+        data: TupleAny,
     ):
         """Row objects are constructed by CursorResult objects."""
         object.__setattr__(self, "_parent", parent)
index dcce3ed342b17a45453178f24e17a1f099098d4e..2706bbe0ee7b2f7e4dff9c71578c72c9571181f9 100644 (file)
@@ -43,6 +43,9 @@ from .. import log
 from .. import util
 from ..sql import compiler
 from ..sql import util as sql_util
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from . import CursorResult
@@ -80,6 +83,7 @@ if typing.TYPE_CHECKING:
 
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 _EMPTY_EXECUTION_OPTS: _ExecuteOptions = util.EMPTY_DICT
 NO_OPTIONS: Mapping[str, Any] = util.EMPTY_DICT
 
@@ -1258,7 +1262,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
     @overload
     def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         parameters: Optional[_CoreSingleExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
@@ -1307,7 +1311,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
     @overload
     def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
@@ -1352,11 +1356,11 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
     @overload
     def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[_T]:
+    ) -> CursorResult[Unpack[_Ts]]:
         ...
 
     @overload
@@ -1366,7 +1370,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     def execute(
@@ -1375,7 +1379,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         r"""Executes a SQL statement construct and returns a
         :class:`_engine.CursorResult`.
 
@@ -1424,7 +1428,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         func: FunctionElement[Any],
         distilled_parameters: _CoreMultiExecuteParams,
         execution_options: CoreExecuteOptionsParameter,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """Execute a sql.FunctionElement object."""
 
         return self._execute_clauseelement(
@@ -1495,7 +1499,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         ddl: ExecutableDDLElement,
         distilled_parameters: _CoreMultiExecuteParams,
         execution_options: CoreExecuteOptionsParameter,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """Execute a schema.DDL object."""
 
         exec_opts = ddl._execution_options.merge_with(
@@ -1590,7 +1594,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         elem: Executable,
         distilled_parameters: _CoreMultiExecuteParams,
         execution_options: CoreExecuteOptionsParameter,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """Execute a sql.ClauseElement object."""
 
         execution_options = elem._execution_options.merge_with(
@@ -1663,7 +1667,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         compiled: Compiled,
         distilled_parameters: _CoreMultiExecuteParams,
         execution_options: CoreExecuteOptionsParameter = _EMPTY_EXECUTION_OPTS,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """Execute a sql.Compiled object.
 
         TODO: why do we have this?   likely deprecate or remove
@@ -1713,7 +1717,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         statement: str,
         parameters: Optional[_DBAPIAnyExecuteParams] = None,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         r"""Executes a string SQL statement on the DBAPI cursor directly,
         without any SQL compilation steps.
 
@@ -1795,7 +1799,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         execution_options: _ExecuteOptions,
         *args: Any,
         **kw: Any,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """Create an :class:`.ExecutionContext` and execute, returning
         a :class:`_engine.CursorResult`."""
 
@@ -1854,7 +1858,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         context: ExecutionContext,
         statement: Union[str, Compiled],
         parameters: Optional[_AnyMultiExecuteParams],
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """continue the _execute_context() method for a single DBAPI
         cursor.execute() or cursor.executemany() call.
 
@@ -1994,7 +1998,7 @@ class Connection(ConnectionEventsTarget, inspection.Inspectable["Inspector"]):
         self,
         dialect: Dialect,
         context: ExecutionContext,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         """continue the _execute_context() method for an "insertmanyvalues"
         operation, which will invoke DBAPI
         cursor.execute() one or more times with individual log and
index a46a9af16ffc336bb7bfa7ef57eeeefcbb57653f..c56065bfe6f6d504403f744c587a73f86ce23f61 100644 (file)
@@ -28,7 +28,6 @@ from typing import Optional
 from typing import Sequence
 from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import TypeVar
 from typing import Union
 
 from .result import IteratorResult
@@ -53,6 +52,9 @@ from ..sql.type_api import TypeEngine
 from ..util import compat
 from ..util.typing import Literal
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 
 if typing.TYPE_CHECKING:
@@ -71,7 +73,7 @@ if typing.TYPE_CHECKING:
     from ..sql.type_api import _ResultProcessorType
 
 
-_T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 # metadata entry tuple indexes.
@@ -344,7 +346,7 @@ class CursorResultMetaData(ResultMetaData):
 
     def __init__(
         self,
-        parent: CursorResult[Any],
+        parent: CursorResult[Unpack[TupleAny]],
         cursor_description: _DBAPICursorDescription,
     ):
         context = parent.context
@@ -928,18 +930,22 @@ class ResultFetchStrategy:
     alternate_cursor_description: Optional[_DBAPICursorDescription] = None
 
     def soft_close(
-        self, result: CursorResult[Any], dbapi_cursor: Optional[DBAPICursor]
+        self,
+        result: CursorResult[Unpack[TupleAny]],
+        dbapi_cursor: Optional[DBAPICursor],
     ) -> None:
         raise NotImplementedError()
 
     def hard_close(
-        self, result: CursorResult[Any], dbapi_cursor: Optional[DBAPICursor]
+        self,
+        result: CursorResult[Unpack[TupleAny]],
+        dbapi_cursor: Optional[DBAPICursor],
     ) -> None:
         raise NotImplementedError()
 
     def yield_per(
         self,
-        result: CursorResult[Any],
+        result: CursorResult[Unpack[TupleAny]],
         dbapi_cursor: Optional[DBAPICursor],
         num: int,
     ) -> None:
@@ -947,7 +953,7 @@ class ResultFetchStrategy:
 
     def fetchone(
         self,
-        result: CursorResult[Any],
+        result: CursorResult[Unpack[TupleAny]],
         dbapi_cursor: DBAPICursor,
         hard_close: bool = False,
     ) -> Any:
@@ -955,7 +961,7 @@ class ResultFetchStrategy:
 
     def fetchmany(
         self,
-        result: CursorResult[Any],
+        result: CursorResult[Unpack[TupleAny]],
         dbapi_cursor: DBAPICursor,
         size: Optional[int] = None,
     ) -> Any:
@@ -963,14 +969,14 @@ class ResultFetchStrategy:
 
     def fetchall(
         self,
-        result: CursorResult[Any],
+        result: CursorResult[Unpack[TupleAny]],
         dbapi_cursor: DBAPICursor,
     ) -> Any:
         raise NotImplementedError()
 
     def handle_exception(
         self,
-        result: CursorResult[Any],
+        result: CursorResult[Unpack[TupleAny]],
         dbapi_cursor: Optional[DBAPICursor],
         err: BaseException,
     ) -> NoReturn:
@@ -1375,7 +1381,7 @@ def null_dml_result() -> IteratorResult[Any]:
     return it
 
 
-class CursorResult(Result[_T]):
+class CursorResult(Result[Unpack[_Ts]]):
     """A Result that is representing state from a DBAPI cursor.
 
     .. versionchanged:: 1.4  The :class:`.CursorResult``
@@ -2108,7 +2114,9 @@ class CursorResult(Result[_T]):
     def _raw_row_iterator(self):
         return self._fetchiter_impl()
 
-    def merge(self, *others: Result[Any]) -> MergedResult[Any]:
+    def merge(
+        self, *others: Result[Unpack[TupleAny]]
+    ) -> MergedResult[Unpack[TupleAny]]:
         merged_result = super().merge(*others)
         setup_rowcounts = self.context._has_rowcount
         if setup_rowcounts:
index afbda08b46172eaea273c14cacb0aa796a096f85..4e4561df38e708296e6c974398b3559274eea381 100644 (file)
@@ -66,6 +66,9 @@ from ..sql.compiler import InsertmanyvaluesSentinelOpts
 from ..sql.compiler import SQLCompiler
 from ..sql.elements import quoted_name
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
+
 
 if typing.TYPE_CHECKING:
     from types import ModuleType
@@ -1187,7 +1190,7 @@ class DefaultExecutionContext(ExecutionContext):
     result_column_struct: Optional[
         Tuple[List[ResultColumnsEntry], bool, bool, bool, bool]
     ] = None
-    returned_default_rows: Optional[Sequence[Row[Any]]] = None
+    returned_default_rows: Optional[Sequence[Row[Unpack[TupleAny]]]] = None
 
     execution_options: _ExecuteOptions = util.EMPTY_DICT
 
index b8e8936b94c4eeca251881e77fc0000e5b2d5b39..2416cd989ffe720e5d7a73bc34798026fcdc8cc0 100644 (file)
@@ -25,6 +25,8 @@ from .interfaces import Dialect
 from .. import event
 from .. import exc
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from .interfaces import _CoreMultiExecuteParams
@@ -270,7 +272,7 @@ class ConnectionEvents(event.Events[ConnectionEventsTarget]):
         multiparams: _CoreMultiExecuteParams,
         params: _CoreSingleExecuteParams,
         execution_options: _ExecuteOptions,
-        result: Result[Any],
+        result: Result[Unpack[TupleAny]],
     ) -> None:
         """Intercept high level execute() events after execute.
 
index f1c18cf456fdfb5bde33e2167004aa8609a9a3b5..b74b9d343b15a57d3cd743f2384428259b654ef1 100644 (file)
@@ -40,11 +40,15 @@ from .. import util
 from ..sql.base import _generative
 from ..sql.base import HasMemoized
 from ..sql.base import InPlaceGenerative
+from ..util import deprecated
 from ..util import HasMemoized_ro_memoized_attribute
 from ..util import NONE_SET
 from ..util._has_cy import HAS_CYEXTENSION
 from ..util.typing import Literal
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
     from ._py_row import tuplegetter as tuplegetter
@@ -64,25 +68,23 @@ _KeyMapRecType = Any
 _KeyMapType = Mapping[_KeyType, _KeyMapRecType]
 
 
-_RowData = Union[Row[Any], RowMapping, Any]
+_RowData = Union[Row[Unpack[TupleAny]], RowMapping, Any]
 """A generic form of "row" that accommodates for the different kinds of
 "rows" that different result objects return, including row, row mapping, and
 scalar values"""
 
-_RawRowType = Tuple[Any, ...]
-"""represents the kind of row we get from a DBAPI cursor"""
 
 _R = TypeVar("_R", bound=_RowData)
 _T = TypeVar("_T", bound=Any)
-_TP = TypeVar("_TP", bound=Tuple[Any, ...])
+_Ts = TypeVarTuple("_Ts")
 
-_InterimRowType = Union[_R, _RawRowType]
+_InterimRowType = Union[_R, TupleAny]
 """a catchall "anything" kind of return type that can be applied
 across all the result types
 
 """
 
-_InterimSupportsScalarsRowType = Union[Row[Any], Any]
+_InterimSupportsScalarsRowType = Union[Row[Unpack[TupleAny]], Any]
 
 _ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
 _TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
@@ -168,7 +170,7 @@ class ResultMetaData:
 
     def _getter(
         self, key: Any, raiseerr: bool = True
-    ) -> Optional[Callable[[Row[Any]], Any]]:
+    ) -> Optional[Callable[[Row[Unpack[TupleAny]]], Any]]:
         index = self._index_for_key(key, raiseerr)
 
         if index is not None:
@@ -391,7 +393,7 @@ class SimpleResultMetaData(ResultMetaData):
 
 def result_tuple(
     fields: Sequence[str], extra: Optional[Any] = None
-) -> Callable[[Iterable[Any]], Row[Any]]:
+) -> Callable[[Iterable[Any]], Row[Unpack[TupleAny]]]:
     parent = SimpleResultMetaData(fields, extra)
     return functools.partial(
         Row, parent, parent._effective_processors, parent._key_to_index
@@ -411,7 +413,7 @@ _NO_ROW = _NoRow._NO_ROW
 class ResultInternal(InPlaceGenerative, Generic[_R]):
     __slots__ = ()
 
-    _real_result: Optional[Result[Any]] = None
+    _real_result: Optional[Result[Unpack[TupleAny]]] = None
     _generate_rows: bool = True
     _row_logging_fn: Optional[Callable[[Any], Any]]
 
@@ -423,20 +425,24 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
 
     _source_supports_scalars: bool
 
-    def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
+    def _fetchiter_impl(
+        self,
+    ) -> Iterator[_InterimRowType[Row[Unpack[TupleAny]]]]:
         raise NotImplementedError()
 
     def _fetchone_impl(
         self, hard_close: bool = False
-    ) -> Optional[_InterimRowType[Row[Any]]]:
+    ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
         raise NotImplementedError()
 
     def _fetchmany_impl(
         self, size: Optional[int] = None
-    ) -> List[_InterimRowType[Row[Any]]]:
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         raise NotImplementedError()
 
-    def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
+    def _fetchall_impl(
+        self,
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         raise NotImplementedError()
 
     def _soft_close(self, hard: bool = False) -> None:
@@ -444,10 +450,10 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
 
     @HasMemoized_ro_memoized_attribute
     def _row_getter(self) -> Optional[Callable[..., _R]]:
-        real_result: Result[Any] = (
+        real_result: Result[Unpack[TupleAny]] = (
             self._real_result
             if self._real_result
-            else cast("Result[Any]", self)
+            else cast("Result[Unpack[TupleAny]]", self)
         )
 
         if real_result._source_supports_scalars:
@@ -461,7 +467,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
                     processors: Optional[_ProcessorsType],
                     key_to_index: Mapping[_KeyType, int],
                     scalar_obj: Any,
-                ) -> Row[Any]:
+                ) -> Row[Unpack[TupleAny]]:
                     return _proc(
                         metadata, processors, key_to_index, (scalar_obj,)
                     )
@@ -485,7 +491,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
 
             fixed_tf = tf
 
-            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
+            def make_row(row: _InterimRowType[Row[Unpack[TupleAny]]]) -> _R:
                 return _make_row_orig(fixed_tf(row))
 
         else:
@@ -497,7 +503,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
             _log_row = real_result._row_logging_fn
             _make_row = make_row
 
-            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
+            def make_row(row: _InterimRowType[Row[Unpack[TupleAny]]]) -> _R:
                 return _log_row(_make_row(row))  # type: ignore
 
         return make_row
@@ -511,7 +517,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
         if self._unique_filter_state:
             uniques, strategy = self._unique_strategy
 
-            def iterrows(self: Result[Any]) -> Iterator[_R]:
+            def iterrows(self: Result[Unpack[TupleAny]]) -> Iterator[_R]:
                 for raw_row in self._fetchiter_impl():
                     obj: _InterimRowType[Any] = (
                         make_row(raw_row) if make_row else raw_row
@@ -526,7 +532,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
 
         else:
 
-            def iterrows(self: Result[Any]) -> Iterator[_R]:
+            def iterrows(self: Result[Unpack[TupleAny]]) -> Iterator[_R]:
                 for raw_row in self._fetchiter_impl():
                     row: _InterimRowType[Any] = (
                         make_row(raw_row) if make_row else raw_row
@@ -591,7 +597,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
         if self._unique_filter_state:
             uniques, strategy = self._unique_strategy
 
-            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
+            def onerow(self: Result[Unpack[TupleAny]]) -> Union[_NoRow, _R]:
                 _onerow = self._fetchone_impl
                 while True:
                     row = _onerow()
@@ -612,7 +618,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
 
         else:
 
-            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
+            def onerow(self: Result[Unpack[TupleAny]]) -> Union[_NoRow, _R]:
                 row = self._fetchone_impl()
                 if row is None:
                     return _NO_ROW
@@ -672,7 +678,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
                     real_result = (
                         self._real_result
                         if self._real_result
-                        else cast("Result[Any]", self)
+                        else cast("Result[Unpack[TupleAny]]", self)
                     )
                     if real_result._yield_per:
                         num_required = num = real_result._yield_per
@@ -712,7 +718,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
                     real_result = (
                         self._real_result
                         if self._real_result
-                        else cast("Result[Any]", self)
+                        else cast("Result[Unpack[TupleAny]]", self)
                     )
                     num = real_result._yield_per
 
@@ -862,7 +868,7 @@ class ResultInternal(InPlaceGenerative, Generic[_R]):
         real_result = (
             self._real_result
             if self._real_result is not None
-            else cast("Result[Any]", self)
+            else cast("Result[Unpack[TupleAny]]", self)
         )
 
         if not strategy and self._metadata._unique_filters:
@@ -906,7 +912,7 @@ class _WithKeys:
         return self._metadata.keys
 
 
-class Result(_WithKeys, ResultInternal[Row[_TP]]):
+class Result(_WithKeys, ResultInternal[Row[Unpack[_Ts]]]):
     """Represent a set of database results.
 
     .. versionadded:: 1.4  The :class:`_engine.Result` object provides a
@@ -934,7 +940,9 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
     __slots__ = ("_metadata", "__dict__")
 
-    _row_logging_fn: Optional[Callable[[Row[Any]], Row[Any]]] = None
+    _row_logging_fn: Optional[
+        Callable[[Row[Unpack[TupleAny]]], Row[Unpack[TupleAny]]]
+    ] = None
 
     _source_supports_scalars: bool = False
 
@@ -1129,12 +1137,12 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         return self._column_slices(col_expressions)
 
     @overload
-    def scalars(self: Result[Tuple[_T]]) -> ScalarResult[_T]:
+    def scalars(self: Result[_T, Unpack[TupleAny]]) -> ScalarResult[_T]:
         ...
 
     @overload
     def scalars(
-        self: Result[Tuple[_T]], index: Literal[0]
+        self: Result[_T, Unpack[TupleAny]], index: Literal[0]
     ) -> ScalarResult[_T]:
         ...
 
@@ -1169,7 +1177,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
     def _getter(
         self, key: _KeyIndexType, raiseerr: bool = True
-    ) -> Optional[Callable[[Row[Any]], Any]]:
+    ) -> Optional[Callable[[Row[Unpack[TupleAny]]], Any]]:
         """return a callable that will retrieve the given key from a
         :class:`_engine.Row`.
 
@@ -1209,7 +1217,12 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         return MappingResult(self)
 
     @property
-    def t(self) -> TupleResult[_TP]:
+    @deprecated(
+        "2.1.0",
+        "The :attr:`.Result.t` method is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
+    def t(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
         """Apply a "typed tuple" typing filter to returned rows.
 
         The :attr:`_engine.Result.t` attribute is a synonym for
@@ -1217,10 +1230,20 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
         .. versionadded:: 2.0
 
+        .. seealso::
+
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
         """
         return self  # type: ignore
 
-    def tuples(self) -> TupleResult[_TP]:
+    @deprecated(
+        "2.1.0",
+        "The :method:`.Result.tuples` method is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
+    def tuples(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
         """Apply a "typed tuple" typing filter to returned rows.
 
         This method returns the same :class:`_engine.Result` object
@@ -1238,6 +1261,9 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
         .. seealso::
 
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
             :attr:`_engine.Result.t` - shorter synonym
 
             :attr:`_engine.Row._t` - :class:`_engine.Row` version
@@ -1255,15 +1281,15 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         """
         raise NotImplementedError()
 
-    def __iter__(self) -> Iterator[Row[_TP]]:
+    def __iter__(self) -> Iterator[Row[Unpack[_Ts]]]:
         return self._iter_impl()
 
-    def __next__(self) -> Row[_TP]:
+    def __next__(self) -> Row[Unpack[_Ts]]:
         return self._next_impl()
 
     def partitions(
         self, size: Optional[int] = None
-    ) -> Iterator[Sequence[Row[_TP]]]:
+    ) -> Iterator[Sequence[Row[Unpack[_Ts]]]]:
         """Iterate through sub-lists of rows of the size given.
 
         Each list will be of the size given, excluding the last list to
@@ -1319,12 +1345,12 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
             else:
                 break
 
-    def fetchall(self) -> Sequence[Row[_TP]]:
+    def fetchall(self) -> Sequence[Row[Unpack[_Ts]]]:
         """A synonym for the :meth:`_engine.Result.all` method."""
 
         return self._allrows()
 
-    def fetchone(self) -> Optional[Row[_TP]]:
+    def fetchone(self) -> Optional[Row[Unpack[_Ts]]]:
         """Fetch one row.
 
         When all rows are exhausted, returns None.
@@ -1346,7 +1372,9 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         else:
             return row
 
-    def fetchmany(self, size: Optional[int] = None) -> Sequence[Row[_TP]]:
+    def fetchmany(
+        self, size: Optional[int] = None
+    ) -> Sequence[Row[Unpack[_Ts]]]:
         """Fetch many rows.
 
         When all rows are exhausted, returns an empty sequence.
@@ -1367,7 +1395,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
         return self._manyrow_getter(self, size)
 
-    def all(self) -> Sequence[Row[_TP]]:
+    def all(self) -> Sequence[Row[Unpack[_Ts]]]:
         """Return all rows in a sequence.
 
         Closes the result set after invocation.   Subsequent invocations
@@ -1386,7 +1414,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
         return self._allrows()
 
-    def first(self) -> Optional[Row[_TP]]:
+    def first(self) -> Optional[Row[Unpack[_Ts]]]:
         """Fetch the first row or ``None`` if no row is present.
 
         Closes the result set and discards remaining rows.
@@ -1425,7 +1453,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
             raise_for_second_row=False, raise_for_none=False, scalar=False
         )
 
-    def one_or_none(self) -> Optional[Row[_TP]]:
+    def one_or_none(self) -> Optional[Row[Unpack[_Ts]]]:
         """Return at most one result or raise an exception.
 
         Returns ``None`` if the result has no rows.
@@ -1451,7 +1479,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         )
 
     @overload
-    def scalar_one(self: Result[Tuple[_T]]) -> _T:
+    def scalar_one(self: Result[_T]) -> _T:
         ...
 
     @overload
@@ -1476,7 +1504,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         )
 
     @overload
-    def scalar_one_or_none(self: Result[Tuple[_T]]) -> Optional[_T]:
+    def scalar_one_or_none(self: Result[_T]) -> Optional[_T]:
         ...
 
     @overload
@@ -1500,7 +1528,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
             raise_for_second_row=True, raise_for_none=False, scalar=True
         )
 
-    def one(self) -> Row[_TP]:
+    def one(self) -> Row[Unpack[_Ts]]:
         """Return exactly one row or raise an exception.
 
         Raises :class:`.NoResultFound` if the result returns no
@@ -1534,7 +1562,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
         )
 
     @overload
-    def scalar(self: Result[Tuple[_T]]) -> Optional[_T]:
+    def scalar(self: Result[_T]) -> Optional[_T]:
         ...
 
     @overload
@@ -1559,7 +1587,7 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
             raise_for_second_row=False, raise_for_none=False, scalar=True
         )
 
-    def freeze(self) -> FrozenResult[_TP]:
+    def freeze(self) -> FrozenResult[Unpack[_Ts]]:
         """Return a callable object that will produce copies of this
         :class:`_engine.Result` when invoked.
 
@@ -1582,7 +1610,9 @@ class Result(_WithKeys, ResultInternal[Row[_TP]]):
 
         return FrozenResult(self)
 
-    def merge(self, *others: Result[Any]) -> MergedResult[_TP]:
+    def merge(
+        self, *others: Result[Unpack[TupleAny]]
+    ) -> MergedResult[Unpack[TupleAny]]:
         """Merge this :class:`_engine.Result` with other compatible result
         objects.
 
@@ -1619,7 +1649,7 @@ class FilterResult(ResultInternal[_R]):
 
     _post_creational_filter: Optional[Callable[[Any], Any]]
 
-    _real_result: Result[Any]
+    _real_result: Result[Unpack[TupleAny]]
 
     def __enter__(self) -> Self:
         return self
@@ -1678,20 +1708,24 @@ class FilterResult(ResultInternal[_R]):
     def _attributes(self) -> Dict[Any, Any]:
         return self._real_result._attributes
 
-    def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
+    def _fetchiter_impl(
+        self,
+    ) -> Iterator[_InterimRowType[Row[Unpack[TupleAny]]]]:
         return self._real_result._fetchiter_impl()
 
     def _fetchone_impl(
         self, hard_close: bool = False
-    ) -> Optional[_InterimRowType[Row[Any]]]:
+    ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
         return self._real_result._fetchone_impl(hard_close=hard_close)
 
-    def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
+    def _fetchall_impl(
+        self,
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         return self._real_result._fetchall_impl()
 
     def _fetchmany_impl(
         self, size: Optional[int] = None
-    ) -> List[_InterimRowType[Row[Any]]]:
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         return self._real_result._fetchmany_impl(size=size)
 
 
@@ -1717,7 +1751,9 @@ class ScalarResult(FilterResult[_R]):
 
     _post_creational_filter: Optional[Callable[[Any], Any]]
 
-    def __init__(self, real_result: Result[Any], index: _KeyIndexType):
+    def __init__(
+        self, real_result: Result[Unpack[TupleAny]], index: _KeyIndexType
+    ):
         self._real_result = real_result
 
         if real_result._source_supports_scalars:
@@ -2010,7 +2046,7 @@ class MappingResult(_WithKeys, FilterResult[RowMapping]):
 
     _post_creational_filter = operator.attrgetter("_mapping")
 
-    def __init__(self, result: Result[Any]):
+    def __init__(self, result: Result[Unpack[TupleAny]]):
         self._real_result = result
         self._unique_filter_state = result._unique_filter_state
         self._metadata = result._metadata
@@ -2137,7 +2173,7 @@ class MappingResult(_WithKeys, FilterResult[RowMapping]):
         )
 
 
-class FrozenResult(Generic[_TP]):
+class FrozenResult(Generic[Unpack[_Ts]]):
     """Represents a :class:`_engine.Result` object in a "frozen" state suitable
     for caching.
 
@@ -2178,7 +2214,7 @@ class FrozenResult(Generic[_TP]):
 
     data: Sequence[Any]
 
-    def __init__(self, result: Result[_TP]):
+    def __init__(self, result: Result[Unpack[_Ts]]):
         self.metadata = result._metadata._for_freeze()
         self._source_supports_scalars = result._source_supports_scalars
         self._attributes = result._attributes
@@ -2195,21 +2231,21 @@ class FrozenResult(Generic[_TP]):
             return [list(row) for row in self.data]
 
     def with_new_rows(
-        self, tuple_data: Sequence[Row[_TP]]
-    ) -> FrozenResult[_TP]:
+        self, tuple_data: Sequence[Row[Unpack[_Ts]]]
+    ) -> FrozenResult[Unpack[_Ts]]:
         fr = FrozenResult.__new__(FrozenResult)
         fr.metadata = self.metadata
         fr._attributes = self._attributes
         fr._source_supports_scalars = self._source_supports_scalars
 
         if self._source_supports_scalars:
-            fr.data = [d[0] for d in tuple_data]
+            fr.data = [d[0] for d in tuple_data]  # type: ignore[misc]
         else:
             fr.data = tuple_data
         return fr
 
-    def __call__(self) -> Result[_TP]:
-        result: IteratorResult[_TP] = IteratorResult(
+    def __call__(self) -> Result[Unpack[_Ts]]:
+        result: IteratorResult[Unpack[_Ts]] = IteratorResult(
             self.metadata, iter(self.data)
         )
         result._attributes = self._attributes
@@ -2217,7 +2253,7 @@ class FrozenResult(Generic[_TP]):
         return result
 
 
-class IteratorResult(Result[_TP]):
+class IteratorResult(Result[Unpack[_Ts]]):
     """A :class:`_engine.Result` that gets data from a Python iterator of
     :class:`_engine.Row` objects or similar row-like data.
 
@@ -2272,7 +2308,7 @@ class IteratorResult(Result[_TP]):
 
     def _fetchone_impl(
         self, hard_close: bool = False
-    ) -> Optional[_InterimRowType[Row[Any]]]:
+    ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
         if self._hard_closed:
             self._raise_hard_closed()
 
@@ -2283,7 +2319,9 @@ class IteratorResult(Result[_TP]):
         else:
             return row
 
-    def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
+    def _fetchall_impl(
+        self,
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         if self._hard_closed:
             self._raise_hard_closed()
         try:
@@ -2293,7 +2331,7 @@ class IteratorResult(Result[_TP]):
 
     def _fetchmany_impl(
         self, size: Optional[int] = None
-    ) -> List[_InterimRowType[Row[Any]]]:
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         if self._hard_closed:
             self._raise_hard_closed()
 
@@ -2304,7 +2342,7 @@ def null_result() -> IteratorResult[Any]:
     return IteratorResult(SimpleResultMetaData([]), iter([]))
 
 
-class ChunkedIteratorResult(IteratorResult[_TP]):
+class ChunkedIteratorResult(IteratorResult[Unpack[_Ts]]):
     """An :class:`_engine.IteratorResult` that works from an
     iterator-producing callable.
 
@@ -2355,13 +2393,13 @@ class ChunkedIteratorResult(IteratorResult[_TP]):
 
     def _fetchmany_impl(
         self, size: Optional[int] = None
-    ) -> List[_InterimRowType[Row[Any]]]:
+    ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
         if self.dynamic_yield_per:
             self.iterator = itertools.chain.from_iterable(self.chunks(size))
         return super()._fetchmany_impl(size=size)
 
 
-class MergedResult(IteratorResult[_TP]):
+class MergedResult(IteratorResult[Unpack[_Ts]]):
     """A :class:`_engine.Result` that is merged from any number of
     :class:`_engine.Result` objects.
 
@@ -2375,7 +2413,9 @@ class MergedResult(IteratorResult[_TP]):
     rowcount: Optional[int]
 
     def __init__(
-        self, cursor_metadata: ResultMetaData, results: Sequence[Result[_TP]]
+        self,
+        cursor_metadata: ResultMetaData,
+        results: Sequence[Result[Unpack[_Ts]]],
     ):
         self._results = results
         super().__init__(
index f62093522888d1e83a8737ae74cf909f27eff579..5e6db0599e58ccdcd6fcc030dc279cb06037c3ab 100644 (file)
@@ -22,16 +22,16 @@ from typing import List
 from typing import Mapping
 from typing import NoReturn
 from typing import Optional
-from typing import overload
 from typing import Sequence
 from typing import Tuple
 from typing import TYPE_CHECKING
 from typing import TypeVar
-from typing import Union
 
 from ..sql import util as sql_util
 from ..util import deprecated
 from ..util._has_cy import HAS_CYEXTENSION
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 if TYPE_CHECKING or not HAS_CYEXTENSION:
     from ._py_row import BaseRow as BaseRow
@@ -39,15 +39,20 @@ else:
     from sqlalchemy.cyextension.resultproxy import BaseRow as BaseRow
 
 if TYPE_CHECKING:
+    from typing import Tuple as _RowBase
+
     from .result import _KeyType
     from .result import _ProcessorsType
     from .result import RMKeyView
+else:
+    _RowBase = Sequence
+
 
 _T = TypeVar("_T", bound=Any)
-_TP = TypeVar("_TP", bound=Tuple[Any, ...])
+_Ts = TypeVarTuple("_Ts")
 
 
-class Row(BaseRow, Sequence[Any], Generic[_TP]):
+class Row(BaseRow, _RowBase[Unpack[_Ts]], Generic[Unpack[_Ts]]):
     """Represent a single result row.
 
     The :class:`.Row` object represents a row of a database result.  It is
@@ -83,7 +88,12 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
     def __delattr__(self, name: str) -> NoReturn:
         raise AttributeError("can't delete attribute")
 
-    def _tuple(self) -> _TP:
+    @deprecated(
+        "2.1.0",
+        "The :meth:`.Row._tuple` method is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
+    def _tuple(self) -> Tuple[Unpack[_Ts]]:
         """Return a 'tuple' form of this :class:`.Row`.
 
         At runtime, this method returns "self"; the :class:`.Row` object is
@@ -99,13 +109,16 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
 
         .. seealso::
 
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
             :attr:`.Row._t` - shorthand attribute notation
 
             :meth:`.Result.tuples`
 
 
         """
-        return self  # type: ignore
+        return self
 
     @deprecated(
         "2.0.19",
@@ -114,16 +127,26 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
         "methods and library-level attributes are intended to be underscored "
         "to avoid name conflicts.  Please use :meth:`Row._tuple`.",
     )
-    def tuple(self) -> _TP:
+    def tuple(self) -> Tuple[Unpack[_Ts]]:
         """Return a 'tuple' form of this :class:`.Row`.
 
         .. versionadded:: 2.0
 
+        .. seealso::
+
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
         """
         return self._tuple()
 
     @property
-    def _t(self) -> _TP:
+    @deprecated(
+        "2.1.0",
+        "The :attr:`.Row._t` attribute is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
+    def _t(self) -> Tuple[Unpack[_Ts]]:
         """A synonym for :meth:`.Row._tuple`.
 
         .. versionadded:: 2.0.19 - The :attr:`.Row._t` attribute supersedes
@@ -133,9 +156,12 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
 
         .. seealso::
 
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
             :attr:`.Result.t`
         """
-        return self  # type: ignore
+        return self
 
     @property
     @deprecated(
@@ -145,11 +171,16 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
         "methods and library-level attributes are intended to be underscored "
         "to avoid name conflicts.  Please use :attr:`Row._t`.",
     )
-    def t(self) -> _TP:
+    def t(self) -> Tuple[Unpack[_Ts]]:
         """A synonym for :meth:`.Row._tuple`.
 
         .. versionadded:: 2.0
 
+        .. seealso::
+
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
         """
         return self._t
 
@@ -172,7 +203,7 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
 
     def _filter_on_values(
         self, processor: Optional[_ProcessorsType]
-    ) -> Row[Any]:
+    ) -> Row[Unpack[_Ts]]:
         return Row(self._parent, processor, self._key_to_index, self._data)
 
     if not TYPE_CHECKING:
@@ -210,19 +241,6 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]):
 
     __hash__ = BaseRow.__hash__
 
-    if TYPE_CHECKING:
-
-        @overload
-        def __getitem__(self, index: int) -> Any:
-            ...
-
-        @overload
-        def __getitem__(self, index: slice) -> Sequence[Any]:
-            ...
-
-        def __getitem__(self, index: Union[int, slice]) -> Any:
-            ...
-
     def __lt__(self, other: Any) -> bool:
         return self._op(other, operator.lt)
 
index 02b70ecd583389331f1b8eeb5f507d7ec8913187..817594e148bb1fd428cc7d0246249a801b225271 100644 (file)
@@ -16,7 +16,6 @@ from typing import Generator
 from typing import NoReturn
 from typing import Optional
 from typing import overload
-from typing import Tuple
 from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
@@ -41,6 +40,9 @@ from ...engine.base import NestedTransaction
 from ...engine.base import Transaction
 from ...exc import ArgumentError
 from ...util.concurrency import greenlet_spawn
+from ...util.typing import TupleAny
+from ...util.typing import TypeVarTuple
+from ...util.typing import Unpack
 
 if TYPE_CHECKING:
     from ...engine.cursor import CursorResult
@@ -62,6 +64,7 @@ if TYPE_CHECKING:
     from ...sql.selectable import TypedReturnsRows
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 def create_async_engine(url: Union[str, URL], **kw: Any) -> AsyncEngine:
@@ -514,11 +517,11 @@ class AsyncConnection(
     @overload
     def stream(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> GeneratorStartableContext[AsyncResult[_T]]:
+    ) -> GeneratorStartableContext[AsyncResult[Unpack[_Ts]]]:
         ...
 
     @overload
@@ -528,7 +531,7 @@ class AsyncConnection(
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> GeneratorStartableContext[AsyncResult[Any]]:
+    ) -> GeneratorStartableContext[AsyncResult[Unpack[TupleAny]]]:
         ...
 
     @asyncstartablecontext
@@ -538,7 +541,7 @@ class AsyncConnection(
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> AsyncIterator[AsyncResult[Any]]:
+    ) -> AsyncIterator[AsyncResult[Unpack[TupleAny]]]:
         """Execute a statement and return an awaitable yielding a
         :class:`_asyncio.AsyncResult` object.
 
@@ -601,11 +604,11 @@ class AsyncConnection(
     @overload
     async def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[_T]:
+    ) -> CursorResult[Unpack[_Ts]]:
         ...
 
     @overload
@@ -615,7 +618,7 @@ class AsyncConnection(
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     async def execute(
@@ -624,7 +627,7 @@ class AsyncConnection(
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         r"""Executes a SQL statement construct and return a buffered
         :class:`_engine.Result`.
 
@@ -668,7 +671,7 @@ class AsyncConnection(
     @overload
     async def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         parameters: Optional[_CoreSingleExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
@@ -710,7 +713,7 @@ class AsyncConnection(
     @overload
     async def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         parameters: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
@@ -753,7 +756,7 @@ class AsyncConnection(
     @overload
     def stream_scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         parameters: Optional[_CoreSingleExecuteParams] = None,
         *,
         execution_options: Optional[CoreExecuteOptionsParameter] = None,
index 2f664bcd6236b683bf3218c34e0b1df25f2fc1ee..14c0840d950d08cbd85c84a156fc022aec93b798 100644 (file)
@@ -28,9 +28,13 @@ from ...engine.result import ResultMetaData
 from ...engine.row import Row
 from ...engine.row import RowMapping
 from ...sql.base import _generative
+from ...util import deprecated
 from ...util.concurrency import greenlet_spawn
 from ...util.typing import Literal
 from ...util.typing import Self
+from ...util.typing import TupleAny
+from ...util.typing import TypeVarTuple
+from ...util.typing import Unpack
 
 if TYPE_CHECKING:
     from ...engine import CursorResult
@@ -38,13 +42,13 @@ if TYPE_CHECKING:
     from ...engine.result import _UniqueFilterType
 
 _T = TypeVar("_T", bound=Any)
-_TP = TypeVar("_TP", bound=Tuple[Any, ...])
+_Ts = TypeVarTuple("_Ts")
 
 
 class AsyncCommon(FilterResult[_R]):
     __slots__ = ()
 
-    _real_result: Result[Any]
+    _real_result: Result[Unpack[TupleAny]]
     _metadata: ResultMetaData
 
     async def close(self) -> None:  # type: ignore[override]
@@ -63,7 +67,7 @@ class AsyncCommon(FilterResult[_R]):
         return self._real_result.closed
 
 
-class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
+class AsyncResult(_WithKeys, AsyncCommon[Row[Unpack[_Ts]]]):
     """An asyncio wrapper around a :class:`_result.Result` object.
 
     The :class:`_asyncio.AsyncResult` only applies to statement executions that
@@ -86,9 +90,9 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
     __slots__ = ()
 
-    _real_result: Result[_TP]
+    _real_result: Result[Unpack[_Ts]]
 
-    def __init__(self, real_result: Result[_TP]):
+    def __init__(self, real_result: Result[Unpack[_Ts]]):
         self._real_result = real_result
 
         self._metadata = real_result._metadata
@@ -103,7 +107,12 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
             )
 
     @property
-    def t(self) -> AsyncTupleResult[_TP]:
+    @deprecated(
+        "2.1.0",
+        "The :attr:`.AsyncResult.t` attribute is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
+    def t(self) -> AsyncTupleResult[Tuple[Unpack[_Ts]]]:
         """Apply a "typed tuple" typing filter to returned rows.
 
         The :attr:`_asyncio.AsyncResult.t` attribute is a synonym for
@@ -111,10 +120,21 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
         .. versionadded:: 2.0
 
+        .. seealso::
+
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
         """
         return self  # type: ignore
 
-    def tuples(self) -> AsyncTupleResult[_TP]:
+    @deprecated(
+        "2.1.0",
+        "The :method:`.AsyncResult.tuples` method is deprecated, "
+        ":class:`.Row` now behaves like a tuple and can unpack types "
+        "directly.",
+    )
+    def tuples(self) -> AsyncTupleResult[Tuple[Unpack[_Ts]]]:
         """Apply a "typed tuple" typing filter to returned rows.
 
         This method returns the same :class:`_asyncio.AsyncResult` object
@@ -132,6 +152,9 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
         .. seealso::
 
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
             :attr:`_asyncio.AsyncResult.t` - shorter synonym
 
             :attr:`_engine.Row.t` - :class:`_engine.Row` version
@@ -163,7 +186,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
     async def partitions(
         self, size: Optional[int] = None
-    ) -> AsyncIterator[Sequence[Row[_TP]]]:
+    ) -> AsyncIterator[Sequence[Row[Unpack[_Ts]]]]:
         """Iterate through sub-lists of rows of the size given.
 
         An async iterator is returned::
@@ -188,7 +211,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
             else:
                 break
 
-    async def fetchall(self) -> Sequence[Row[_TP]]:
+    async def fetchall(self) -> Sequence[Row[Unpack[_Ts]]]:
         """A synonym for the :meth:`_asyncio.AsyncResult.all` method.
 
         .. versionadded:: 2.0
@@ -197,7 +220,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
         return await greenlet_spawn(self._allrows)
 
-    async def fetchone(self) -> Optional[Row[_TP]]:
+    async def fetchone(self) -> Optional[Row[Unpack[_Ts]]]:
         """Fetch one row.
 
         When all rows are exhausted, returns None.
@@ -221,7 +244,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
     async def fetchmany(
         self, size: Optional[int] = None
-    ) -> Sequence[Row[_TP]]:
+    ) -> Sequence[Row[Unpack[_Ts]]]:
         """Fetch many rows.
 
         When all rows are exhausted, returns an empty list.
@@ -242,7 +265,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
         return await greenlet_spawn(self._manyrow_getter, self, size)
 
-    async def all(self) -> Sequence[Row[_TP]]:
+    async def all(self) -> Sequence[Row[Unpack[_Ts]]]:
         """Return all rows in a list.
 
         Closes the result set after invocation.   Subsequent invocations
@@ -254,17 +277,17 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
         return await greenlet_spawn(self._allrows)
 
-    def __aiter__(self) -> AsyncResult[_TP]:
+    def __aiter__(self) -> AsyncResult[Unpack[_Ts]]:
         return self
 
-    async def __anext__(self) -> Row[_TP]:
+    async def __anext__(self) -> Row[Unpack[_Ts]]:
         row = await greenlet_spawn(self._onerow_getter, self)
         if row is _NO_ROW:
             raise StopAsyncIteration()
         else:
             return row
 
-    async def first(self) -> Optional[Row[_TP]]:
+    async def first(self) -> Optional[Row[Unpack[_Ts]]]:
         """Fetch the first row or ``None`` if no row is present.
 
         Closes the result set and discards remaining rows.
@@ -300,7 +323,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
         """
         return await greenlet_spawn(self._only_one_row, False, False, False)
 
-    async def one_or_none(self) -> Optional[Row[_TP]]:
+    async def one_or_none(self) -> Optional[Row[Unpack[_Ts]]]:
         """Return at most one result or raise an exception.
 
         Returns ``None`` if the result has no rows.
@@ -324,7 +347,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
         return await greenlet_spawn(self._only_one_row, True, False, False)
 
     @overload
-    async def scalar_one(self: AsyncResult[Tuple[_T]]) -> _T:
+    async def scalar_one(self: AsyncResult[_T]) -> _T:
         ...
 
     @overload
@@ -348,7 +371,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
     @overload
     async def scalar_one_or_none(
-        self: AsyncResult[Tuple[_T]],
+        self: AsyncResult[_T],
     ) -> Optional[_T]:
         ...
 
@@ -371,7 +394,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
         """
         return await greenlet_spawn(self._only_one_row, True, False, True)
 
-    async def one(self) -> Row[_TP]:
+    async def one(self) -> Row[Unpack[_Ts]]:
         """Return exactly one row or raise an exception.
 
         Raises :class:`.NoResultFound` if the result returns no
@@ -403,7 +426,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
         return await greenlet_spawn(self._only_one_row, True, True, False)
 
     @overload
-    async def scalar(self: AsyncResult[Tuple[_T]]) -> Optional[_T]:
+    async def scalar(self: AsyncResult[_T]) -> Optional[_T]:
         ...
 
     @overload
@@ -426,7 +449,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
         """
         return await greenlet_spawn(self._only_one_row, False, False, True)
 
-    async def freeze(self) -> FrozenResult[_TP]:
+    async def freeze(self) -> FrozenResult[Unpack[_Ts]]:
         """Return a callable object that will produce copies of this
         :class:`_asyncio.AsyncResult` when invoked.
 
@@ -451,12 +474,14 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
 
     @overload
     def scalars(
-        self: AsyncResult[Tuple[_T]], index: Literal[0]
+        self: AsyncResult[_T, Unpack[TupleAny]], index: Literal[0]
     ) -> AsyncScalarResult[_T]:
         ...
 
     @overload
-    def scalars(self: AsyncResult[Tuple[_T]]) -> AsyncScalarResult[_T]:
+    def scalars(
+        self: AsyncResult[_T, Unpack[TupleAny]],
+    ) -> AsyncScalarResult[_T]:
         ...
 
     @overload
@@ -513,7 +538,11 @@ class AsyncScalarResult(AsyncCommon[_R]):
 
     _generate_rows = False
 
-    def __init__(self, real_result: Result[Any], index: _KeyIndexType):
+    def __init__(
+        self,
+        real_result: Result[Unpack[TupleAny]],
+        index: _KeyIndexType,
+    ):
         self._real_result = real_result
 
         if real_result._source_supports_scalars:
@@ -644,7 +673,7 @@ class AsyncMappingResult(_WithKeys, AsyncCommon[RowMapping]):
 
     _post_creational_filter = operator.attrgetter("_mapping")
 
-    def __init__(self, result: Result[Any]):
+    def __init__(self, result: Result[Unpack[TupleAny]]):
         self._real_result = result
         self._unique_filter_state = result._unique_filter_state
         self._metadata = result._metadata
@@ -944,7 +973,7 @@ class AsyncTupleResult(AsyncCommon[_R], util.TypingOnly):
             ...
 
 
-_RT = TypeVar("_RT", bound="Result[Any]")
+_RT = TypeVar("_RT", bound="Result[Unpack[TupleAny]]")
 
 
 async def _ensure_sync_result(result: _RT, calling_method: Any) -> _RT:
index a5127b8661304de01cf6c6b23494f0145c9a1cbd..850b4b750f5e92b92273e767e18282289e249c28 100644 (file)
@@ -31,6 +31,9 @@ from ...util import create_proxy_methods
 from ...util import ScopedRegistry
 from ...util import warn
 from ...util import warn_deprecated
+from ...util.typing import TupleAny
+from ...util.typing import TypeVarTuple
+from ...util.typing import Unpack
 
 if TYPE_CHECKING:
     from .engine import AsyncConnection
@@ -61,6 +64,7 @@ if TYPE_CHECKING:
     from ...sql.selectable import TypedReturnsRows
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 @create_proxy_methods(
@@ -529,14 +533,14 @@ class async_scoped_session(Generic[_AS]):
     @overload
     async def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[_T]:
+    ) -> Result[Unpack[_Ts]]:
         ...
 
     @overload
@@ -549,7 +553,7 @@ class async_scoped_session(Generic[_AS]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     @overload
@@ -562,7 +566,7 @@ class async_scoped_session(Generic[_AS]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         ...
 
     async def execute(
@@ -573,7 +577,7 @@ class async_scoped_session(Generic[_AS]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         r"""Execute a statement and return a buffered
         :class:`_engine.Result` object.
 
@@ -1009,7 +1013,7 @@ class async_scoped_session(Generic[_AS]):
     @overload
     async def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -1064,7 +1068,7 @@ class async_scoped_session(Generic[_AS]):
     @overload
     async def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -1207,13 +1211,13 @@ class async_scoped_session(Generic[_AS]):
     @overload
     async def stream(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[_T]:
+    ) -> AsyncResult[Unpack[_Ts]]:
         ...
 
     @overload
@@ -1225,7 +1229,7 @@ class async_scoped_session(Generic[_AS]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[Any]:
+    ) -> AsyncResult[Unpack[TupleAny]]:
         ...
 
     async def stream(
@@ -1236,7 +1240,7 @@ class async_scoped_session(Generic[_AS]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[Any]:
+    ) -> AsyncResult[Unpack[TupleAny]]:
         r"""Execute a statement and return a streaming
         :class:`_asyncio.AsyncResult` object.
 
@@ -1259,7 +1263,7 @@ class async_scoped_session(Generic[_AS]):
     @overload
     async def stream_scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -1593,7 +1597,7 @@ class async_scoped_session(Generic[_AS]):
         ident: Union[Any, Tuple[Any, ...]] = None,
         *,
         instance: Optional[Any] = None,
-        row: Optional[Union[Row[Any], RowMapping]] = None,
+        row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
         identity_token: Optional[Any] = None,
     ) -> _IdentityKeyType[Any]:
         r"""Return an identity key.
index cdca94a9abb2dcdea860b99032119ba443d0c4f8..f7a24698686dd0fd4cbaffff98fcae096042dcc7 100644 (file)
@@ -38,6 +38,10 @@ from ...orm import Session
 from ...orm import SessionTransaction
 from ...orm import state as _instance_state
 from ...util.concurrency import greenlet_spawn
+from ...util.typing import TupleAny
+from ...util.typing import TypeVarTuple
+from ...util.typing import Unpack
+
 
 if TYPE_CHECKING:
     from .engine import AsyncConnection
@@ -72,7 +76,7 @@ if TYPE_CHECKING:
 _AsyncSessionBind = Union["AsyncEngine", "AsyncConnection"]
 
 _T = TypeVar("_T", bound=Any)
-
+_Ts = TypeVarTuple("_Ts")
 
 _EXECUTE_OPTIONS = util.immutabledict({"prebuffer_rows": True})
 _STREAM_OPTIONS = util.immutabledict({"stream_results": True})
@@ -391,14 +395,14 @@ class AsyncSession(ReversibleProxy[Session]):
     @overload
     async def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[_T]:
+    ) -> Result[Unpack[_Ts]]:
         ...
 
     @overload
@@ -411,7 +415,7 @@ class AsyncSession(ReversibleProxy[Session]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     @overload
@@ -424,7 +428,7 @@ class AsyncSession(ReversibleProxy[Session]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         ...
 
     async def execute(
@@ -435,7 +439,7 @@ class AsyncSession(ReversibleProxy[Session]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         """Execute a statement and return a buffered
         :class:`_engine.Result` object.
 
@@ -465,7 +469,7 @@ class AsyncSession(ReversibleProxy[Session]):
     @overload
     async def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -522,7 +526,7 @@ class AsyncSession(ReversibleProxy[Session]):
     @overload
     async def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -649,13 +653,13 @@ class AsyncSession(ReversibleProxy[Session]):
     @overload
     async def stream(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[_T]:
+    ) -> AsyncResult[Unpack[_Ts]]:
         ...
 
     @overload
@@ -667,7 +671,7 @@ class AsyncSession(ReversibleProxy[Session]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[Any]:
+    ) -> AsyncResult[Unpack[TupleAny]]:
         ...
 
     async def stream(
@@ -678,7 +682,7 @@ class AsyncSession(ReversibleProxy[Session]):
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         **kw: Any,
-    ) -> AsyncResult[Any]:
+    ) -> AsyncResult[Unpack[TupleAny]]:
         """Execute a statement and return a streaming
         :class:`_asyncio.AsyncResult` object.
 
@@ -704,7 +708,7 @@ class AsyncSession(ReversibleProxy[Session]):
     @overload
     async def stream_scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -1590,7 +1594,7 @@ class AsyncSession(ReversibleProxy[Session]):
         ident: Union[Any, Tuple[Any, ...]] = None,
         *,
         instance: Optional[Any] = None,
-        row: Optional[Union[Row[Any], RowMapping]] = None,
+        row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
         identity_token: Optional[Any] = None,
     ) -> _IdentityKeyType[Any]:
         r"""Return an identity key.
index 24060a062e1334e513efd3b935adfff64e8003d8..ad8b3444adaf30b007aad30ee04a5988fa4d379c 100644 (file)
@@ -50,12 +50,15 @@ from ..orm.session import _BindArguments
 from ..orm.session import _PKIdentityArgument
 from ..orm.session import Session
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
+
 
 if TYPE_CHECKING:
     from ..engine.base import Connection
     from ..engine.base import Engine
     from ..engine.base import OptionEngine
-    from ..engine.result import IteratorResult
     from ..engine.result import Result
     from ..orm import LoaderCallableStatus
     from ..orm._typing import _O
@@ -66,12 +69,12 @@ if TYPE_CHECKING:
     from ..orm.session import ORMExecuteState
     from ..orm.state import InstanceState
     from ..sql import Executable
-    from ..sql._typing import _TP
     from ..sql.elements import ClauseElement
 
 __all__ = ["ShardedSession", "ShardedQuery"]
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 ShardIdentifier = str
@@ -427,7 +430,7 @@ class set_shard_id(ORMOption):
 
 def execute_and_instances(
     orm_context: ORMExecuteState,
-) -> Union[Result[_T], IteratorResult[_TP]]:
+) -> Result[Unpack[TupleAny]]:
     active_options: Union[
         None,
         QueryContext.default_load_options,
@@ -449,7 +452,7 @@ def execute_and_instances(
 
     def iter_for_shard(
         shard_id: ShardIdentifier,
-    ) -> Union[Result[_T], IteratorResult[_TP]]:
+    ) -> Result[Unpack[TupleAny]]:
         bind_arguments = dict(orm_context.bind_arguments)
         bind_arguments["shard_id"] = shard_id
 
index 3f558d2d405b22562572a7f85f97898628e51c4a..c2ef0980e661fb281d59b2e7e69a3727a937762b 100644 (file)
@@ -53,6 +53,8 @@ from ..sql.dml import InsertDMLState
 from ..sql.dml import UpdateDMLState
 from ..util import EMPTY_DICT
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import DMLStrategyArgument
@@ -249,7 +251,7 @@ def _bulk_update(
     update_changed_only: bool,
     use_orm_update_stmt: Optional[dml.Update] = ...,
     enable_check_rowcount: bool = True,
-) -> _result.Result[Any]:
+) -> _result.Result[Unpack[TupleAny]]:
     ...
 
 
@@ -261,7 +263,7 @@ def _bulk_update(
     update_changed_only: bool,
     use_orm_update_stmt: Optional[dml.Update] = None,
     enable_check_rowcount: bool = True,
-) -> Optional[_result.Result[Any]]:
+) -> Optional[_result.Result[Unpack[TupleAny]]]:
     base_mapper = mapper.base_mapper
 
     search_keys = mapper._primary_key_propkeys
@@ -1236,7 +1238,7 @@ class BulkORMInsert(ORMDMLState, InsertDMLState):
                 "are 'raw', 'orm', 'bulk', 'auto"
             )
 
-        result: _result.Result[Any]
+        result: _result.Result[Unpack[TupleAny]]
 
         if insert_options._dml_strategy == "raw":
             result = conn.execute(
@@ -1572,7 +1574,7 @@ class BulkORMUpdate(BulkUDCompileState, UpdateDMLState):
                 "are 'orm', 'auto', 'bulk', 'core_only'"
             )
 
-        result: _result.Result[Any]
+        result: _result.Result[Unpack[TupleAny]]
 
         if update_options._dml_strategy == "bulk":
             enable_check_rowcount = not statement._where_criteria
index b417825318531fb2adfb428da58fc20c8d487130..b51f2b9613c7773f710a9d73274db37f68117d58 100644 (file)
@@ -46,7 +46,6 @@ from ..sql import expression
 from ..sql import roles
 from ..sql import util as sql_util
 from ..sql import visitors
-from ..sql._typing import _TP
 from ..sql._typing import is_dml
 from ..sql._typing import is_insert_update
 from ..sql._typing import is_select_base
@@ -68,6 +67,10 @@ from ..sql.selectable import SelectLabelStyle
 from ..sql.selectable import SelectState
 from ..sql.selectable import TypedReturnsRows
 from ..sql.visitors import InternalTraversal
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
+
 
 if TYPE_CHECKING:
     from ._typing import _InternalEntityType
@@ -91,6 +94,7 @@ if TYPE_CHECKING:
     from ..sql.type_api import TypeEngine
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 _path_registry = PathRegistry.root
 
 _EMPTY_DICT = util.immutabledict()
@@ -147,7 +151,10 @@ class QueryContext:
     def __init__(
         self,
         compile_state: CompileState,
-        statement: Union[Select[Any], FromStatement[Any]],
+        statement: Union[
+            Select[Unpack[TupleAny]],
+            FromStatement[Unpack[TupleAny]],
+        ],
         params: _CoreSingleExecuteParams,
         session: Session,
         load_options: Union[
@@ -401,8 +408,10 @@ class ORMCompileState(AbstractORMCompileState):
     attributes: Dict[Any, Any]
     global_attributes: Dict[Any, Any]
 
-    statement: Union[Select[Any], FromStatement[Any]]
-    select_statement: Union[Select[Any], FromStatement[Any]]
+    statement: Union[Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]]]
+    select_statement: Union[
+        Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]]
+    ]
     _entities: List[_QueryEntity]
     _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter]
     compile_options: Union[
@@ -416,7 +425,7 @@ class ORMCompileState(AbstractORMCompileState):
     dedupe_columns: Set[ColumnElement[Any]]
     create_eager_joins: List[
         # TODO: this structure is set up by JoinedLoader
-        Tuple[Any, ...]
+        TupleAny
     ]
     current_path: PathRegistry = _path_registry
     _has_mapper_entities = False
@@ -856,7 +865,7 @@ class ORMFromStatementCompileState(ORMCompileState):
             entity.setup_dml_returning_compile_state(self, adapter)
 
 
-class FromStatement(GroupedElement, Generative, TypedReturnsRows[_TP]):
+class FromStatement(GroupedElement, Generative, TypedReturnsRows[Unpack[_Ts]]):
     """Core construct that represents a load of ORM objects from various
     :class:`.ReturnsRows` and other classes including:
 
@@ -2433,7 +2442,7 @@ def _column_descriptions(
 
 
 def _legacy_filter_by_entity_zero(
-    query_or_augmented_select: Union[Query[Any], Select[Any]]
+    query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]]
 ) -> Optional[_InternalEntityType[Any]]:
     self = query_or_augmented_select
     if self._setup_joins:
@@ -2448,7 +2457,7 @@ def _legacy_filter_by_entity_zero(
 
 
 def _entity_from_pre_ent_zero(
-    query_or_augmented_select: Union[Query[Any], Select[Any]]
+    query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]]
 ) -> Optional[_InternalEntityType[Any]]:
     self = query_or_augmented_select
     if not self._raw_columns:
index a70f0b3ec37e85323097c535ff20fa5af6255589..4d5775ee2d3b524dc50ad4ccfc4606ab5ccbf3e2 100644 (file)
@@ -55,7 +55,10 @@ from ..sql import operators
 from ..sql.elements import BindParameter
 from ..util.typing import is_fwd_ref
 from ..util.typing import is_pep593
+from ..util.typing import TupleAny
 from ..util.typing import typing_get_args
+from ..util.typing import Unpack
+
 
 if typing.TYPE_CHECKING:
     from ._typing import _InstanceDict
@@ -713,11 +716,11 @@ class CompositeProperty(
 
         def create_row_processor(
             self,
-            query: Select[Any],
-            procs: Sequence[Callable[[Row[Any]], Any]],
+            query: Select[Unpack[TupleAny]],
+            procs: Sequence[Callable[[Row[Unpack[TupleAny]]], Any]],
             labels: Sequence[str],
-        ) -> Callable[[Row[Any]], Any]:
-            def proc(row: Row[Any]) -> Any:
+        ) -> Callable[[Row[Unpack[TupleAny]]], Any]:
+            def proc(row: Row[Unpack[TupleAny]]) -> Any:
                 return self.property.composite_class(
                     *[proc(row) for proc in procs]
                 )
index 5cab1d348c247e01b81102864decff83b63a90b6..dd9e558cd3069f11b819397cc4588507321004fe 100644 (file)
@@ -72,6 +72,9 @@ from ..sql.schema import Column
 from ..sql.type_api import TypeEngine
 from ..util import warn_deprecated
 from ..util.typing import RODescriptorReference
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
+
 
 if typing.TYPE_CHECKING:
     from ._typing import _EntityType
@@ -486,7 +489,7 @@ class MapperProperty(
         query_entity: _MapperEntity,
         path: AbstractEntityRegistry,
         mapper: Mapper[Any],
-        result: Result[Any],
+        result: Result[Unpack[TupleAny]],
         adapter: Optional[ORMAdapter],
         populators: _PopulatorDict,
     ) -> None:
@@ -1056,7 +1059,7 @@ class StrategizedProperty(MapperProperty[_T]):
         query_entity: _MapperEntity,
         path: AbstractEntityRegistry,
         mapper: Mapper[Any],
-        result: Result[Any],
+        result: Result[Unpack[TupleAny]],
         adapter: Optional[ORMAdapter],
         populators: _PopulatorDict,
     ) -> None:
@@ -1447,7 +1450,7 @@ class LoaderStrategy:
         path: AbstractEntityRegistry,
         loadopt: Optional[_LoadElement],
         mapper: Mapper[Any],
-        result: Result[Any],
+        result: Result[Unpack[TupleAny]],
         adapter: Optional[ORMAdapter],
         populators: _PopulatorDict,
     ) -> None:
index 1de71f9c71ca9f1d99acbb45940d289293ce1f54..b430cbf4241a239111f82bdbde9bf8e0605685a5 100644 (file)
@@ -53,6 +53,8 @@ from ..sql.selectable import ForUpdateArg
 from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from ..sql.selectable import SelectState
 from ..util import EMPTY_DICT
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import _IdentityKeyType
@@ -75,7 +77,9 @@ _new_runid = util.counter()
 _PopulatorDict = Dict[str, List[Tuple[str, Any]]]
 
 
-def instances(cursor: CursorResult[Any], context: QueryContext) -> Result[Any]:
+def instances(
+    cursor: CursorResult[Unpack[TupleAny]], context: QueryContext
+) -> Result[Unpack[TupleAny]]:
     """Return a :class:`.Result` given an ORM query context.
 
     :param cursor: a :class:`.CursorResult`, generated by a statement
index 7ad4fc6be1418e6c964bb3702aded7643e4fe07d..e91b1a6bd0e8962f563a6d4a66a8a8b7f240efb0 100644 (file)
@@ -89,6 +89,8 @@ from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from ..util import HasMemoized
 from ..util import HasMemoized_ro_memoized_attribute
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import _IdentityKeyType
@@ -3428,7 +3430,7 @@ class Mapper(
 
     def identity_key_from_row(
         self,
-        row: Optional[Union[Row[Any], RowMapping]],
+        row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]],
         identity_token: Optional[Any] = None,
         adapter: Optional[ORMAdapter] = None,
     ) -> _IdentityKeyType[_O]:
index 019a658f1e6d17ed9ef373d97c5f3e09496b7aa7..4aaae3ee4f38c09e78a6e656bc6eee89c45c5ed5 100644 (file)
@@ -74,7 +74,6 @@ from ..sql import Select
 from ..sql import util as sql_util
 from ..sql import visitors
 from ..sql._typing import _FromClauseArgument
-from ..sql._typing import _TP
 from ..sql.annotation import SupportsCloneAnnotations
 from ..sql.base import _entity_namespace_key
 from ..sql.base import _generative
@@ -91,8 +90,12 @@ from ..sql.selectable import HasPrefixes
 from ..sql.selectable import HasSuffixes
 from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from ..sql.selectable import SelectLabelStyle
+from ..util import deprecated
 from ..util.typing import Literal
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 
 if TYPE_CHECKING:
@@ -150,6 +153,7 @@ if TYPE_CHECKING:
 __all__ = ["Query", "QueryContext"]
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 @inspection._self_inspects
@@ -295,6 +299,11 @@ class Query(
             for ent in util.to_list(entities)
         ]
 
+    @deprecated(
+        "2.1.0",
+        "The :method:`.Query.tuples` method is deprecated, :class:`.Row` "
+        "now behaves like a tuple and can unpack types directly.",
+    )
     def tuples(self: Query[_O]) -> Query[Tuple[_O]]:
         """return a tuple-typed form of this :class:`.Query`.
 
@@ -316,6 +325,9 @@ class Query(
 
         .. seealso::
 
+            :ref:`change_10635` - describes a migration path from this
+            workaround for SQLAlchemy 2.1.
+
             :meth:`.Result.tuples` - v2 equivalent method.
 
         """
@@ -533,7 +545,9 @@ class Query(
 
         return stmt
 
-    def _final_statement(self, legacy_query_style: bool = True) -> Select[Any]:
+    def _final_statement(
+        self, legacy_query_style: bool = True
+    ) -> Select[Unpack[TupleAny]]:
         """Return the 'final' SELECT statement for this :class:`.Query`.
 
         This is used by the testing suite only and is fairly inefficient.
@@ -822,7 +836,7 @@ class Query(
     @overload
     def only_return_tuples(
         self: Query[_O], value: Literal[True]
-    ) -> RowReturningQuery[Tuple[_O]]:
+    ) -> RowReturningQuery[_O]:
         ...
 
     @overload
@@ -1493,13 +1507,13 @@ class Query(
     @overload
     def with_entities(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1]]:
+    ) -> RowReturningQuery[_T0, _T1]:
         ...
 
     @overload
     def with_entities(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2]:
         ...
 
     @overload
@@ -1510,7 +1524,7 @@ class Query(
         __ent2: _TCCA[_T2],
         __ent3: _TCCA[_T3],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3]:
         ...
 
     @overload
@@ -1522,7 +1536,7 @@ class Query(
         __ent3: _TCCA[_T3],
         __ent4: _TCCA[_T4],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]:
         ...
 
     @overload
@@ -1535,7 +1549,7 @@ class Query(
         __ent4: _TCCA[_T4],
         __ent5: _TCCA[_T5],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]:
         ...
 
     @overload
@@ -1549,7 +1563,7 @@ class Query(
         __ent5: _TCCA[_T5],
         __ent6: _TCCA[_T6],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
         ...
 
     @overload
@@ -1564,7 +1578,10 @@ class Query(
         __ent6: _TCCA[_T6],
         __ent7: _TCCA[_T7],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+        *entities: _ColumnsClauseArgument[Any],
+    ) -> RowReturningQuery[
+        _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+    ]:
         ...
 
     # END OVERLOADED FUNCTIONS self.with_entities
@@ -3414,8 +3431,8 @@ class BulkDelete(BulkUD):
     """BulkUD which handles DELETEs."""
 
 
-class RowReturningQuery(Query[Row[_TP]]):
+class RowReturningQuery(Query[Row[Unpack[_Ts]]]):
     if TYPE_CHECKING:
 
-        def tuples(self) -> Query[_TP]:  # type: ignore
+        def tuples(self) -> Query[Tuple[Unpack[_Ts]]]:  # type: ignore
             ...
index ead18d1862dced1a36dfc49f410cab425684c1a4..2e87f41879e83a39ef745f1888ebecab1ad85b21 100644 (file)
@@ -32,6 +32,9 @@ from ..util import ScopedRegistry
 from ..util import ThreadLocalRegistry
 from ..util import warn
 from ..util import warn_deprecated
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import _EntityType
@@ -75,7 +78,9 @@ if TYPE_CHECKING:
     from ..sql.selectable import ForUpdateParameter
     from ..sql.selectable import TypedReturnsRows
 
+
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 
 class QueryPropertyDescriptor(Protocol):
@@ -675,14 +680,14 @@ class scoped_session(Generic[_S]):
     @overload
     def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[_T]:
+    ) -> Result[Unpack[_Ts]]:
         ...
 
     @overload
@@ -695,7 +700,7 @@ class scoped_session(Generic[_S]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     @overload
@@ -708,7 +713,7 @@ class scoped_session(Generic[_S]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         ...
 
     def execute(
@@ -720,7 +725,7 @@ class scoped_session(Generic[_S]):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         r"""Execute a SQL expression construct.
 
         .. container:: class_bases
@@ -1580,7 +1585,7 @@ class scoped_session(Generic[_S]):
     @overload
     def query(
         self, _colexpr: TypedColumnsClauseRole[_T]
-    ) -> RowReturningQuery[Tuple[_T]]:
+    ) -> RowReturningQuery[_T]:
         ...
 
     # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
@@ -1591,13 +1596,13 @@ class scoped_session(Generic[_S]):
     @overload
     def query(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1]]:
+    ) -> RowReturningQuery[_T0, _T1]:
         ...
 
     @overload
     def query(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2]:
         ...
 
     @overload
@@ -1608,7 +1613,7 @@ class scoped_session(Generic[_S]):
         __ent2: _TCCA[_T2],
         __ent3: _TCCA[_T3],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3]:
         ...
 
     @overload
@@ -1620,7 +1625,7 @@ class scoped_session(Generic[_S]):
         __ent3: _TCCA[_T3],
         __ent4: _TCCA[_T4],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]:
         ...
 
     @overload
@@ -1633,7 +1638,7 @@ class scoped_session(Generic[_S]):
         __ent4: _TCCA[_T4],
         __ent5: _TCCA[_T5],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]:
         ...
 
     @overload
@@ -1647,7 +1652,7 @@ class scoped_session(Generic[_S]):
         __ent5: _TCCA[_T5],
         __ent6: _TCCA[_T6],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
         ...
 
     @overload
@@ -1662,7 +1667,10 @@ class scoped_session(Generic[_S]):
         __ent6: _TCCA[_T6],
         __ent7: _TCCA[_T7],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+        *entities: _ColumnsClauseArgument[Any],
+    ) -> RowReturningQuery[
+        _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+    ]:
         ...
 
     # END OVERLOADED FUNCTIONS self.query
@@ -1817,7 +1825,7 @@ class scoped_session(Generic[_S]):
     @overload
     def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreSingleExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -1872,7 +1880,7 @@ class scoped_session(Generic[_S]):
     @overload
     def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -2158,7 +2166,7 @@ class scoped_session(Generic[_S]):
         ident: Union[Any, Tuple[Any, ...]] = None,
         *,
         instance: Optional[Any] = None,
-        row: Optional[Union[Row[Any], RowMapping]] = None,
+        row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
         identity_token: Optional[Any] = None,
     ) -> _IdentityKeyType[Any]:
         r"""Return an identity key.
index 26c907446e75372d29f6afb693ea284c7ad85bd0..4315ac7f3005d24d319f517caa184dbd2a74a4a0 100644 (file)
@@ -91,6 +91,10 @@ from ..sql.selectable import ForUpdateArg
 from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
 from ..util import IdentitySet
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
+
 
 if typing.TYPE_CHECKING:
     from ._typing import _EntityType
@@ -134,6 +138,7 @@ if typing.TYPE_CHECKING:
     from ..sql.selectable import TypedReturnsRows
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 __all__ = [
     "Session",
@@ -222,7 +227,7 @@ class _SessionClassMethods:
         ident: Union[Any, Tuple[Any, ...]] = None,
         *,
         instance: Optional[Any] = None,
-        row: Optional[Union[Row[Any], RowMapping]] = None,
+        row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
         identity_token: Optional[Any] = None,
     ) -> _IdentityKeyType[Any]:
         """Return an identity key.
@@ -385,7 +390,7 @@ class ORMExecuteState(util.MemoizedSlots):
         params: Optional[_CoreAnyExecuteParams] = None,
         execution_options: Optional[OrmExecuteOptionsParameter] = None,
         bind_arguments: Optional[_BindArguments] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         """Execute the statement represented by this
         :class:`.ORMExecuteState`, without re-invoking events that have
         already proceeded.
@@ -2071,7 +2076,7 @@ class Session(_SessionClassMethods, EventTarget):
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
         _scalar_result: bool = ...,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         ...
 
     def _execute_internal(
@@ -2147,7 +2152,9 @@ class Session(_SessionClassMethods, EventTarget):
             )
             for idx, fn in enumerate(events_todo):
                 orm_exec_state._starting_event_idx = idx
-                fn_result: Optional[Result[Any]] = fn(orm_exec_state)
+                fn_result: Optional[Result[Unpack[TupleAny]]] = fn(
+                    orm_exec_state
+                )
                 if fn_result:
                     if _scalar_result:
                         return fn_result.scalar()
@@ -2187,7 +2194,9 @@ class Session(_SessionClassMethods, EventTarget):
             )
 
         if compile_state_cls:
-            result: Result[Any] = compile_state_cls.orm_execute_statement(
+            result: Result[
+                Unpack[TupleAny]
+            ] = compile_state_cls.orm_execute_statement(
                 self,
                 statement,
                 params or {},
@@ -2208,14 +2217,14 @@ class Session(_SessionClassMethods, EventTarget):
     @overload
     def execute(
         self,
-        statement: TypedReturnsRows[_T],
+        statement: TypedReturnsRows[Unpack[_Ts]],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[_T]:
+    ) -> Result[Unpack[_Ts]]:
         ...
 
     @overload
@@ -2228,7 +2237,7 @@ class Session(_SessionClassMethods, EventTarget):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> CursorResult[Any]:
+    ) -> CursorResult[Unpack[TupleAny]]:
         ...
 
     @overload
@@ -2241,7 +2250,7 @@ class Session(_SessionClassMethods, EventTarget):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         ...
 
     def execute(
@@ -2253,7 +2262,7 @@ class Session(_SessionClassMethods, EventTarget):
         bind_arguments: Optional[_BindArguments] = None,
         _parent_execute_state: Optional[Any] = None,
         _add_event: Optional[Any] = None,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         r"""Execute a SQL expression construct.
 
         Returns a :class:`_engine.Result` object representing
@@ -2317,7 +2326,7 @@ class Session(_SessionClassMethods, EventTarget):
     @overload
     def scalar(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreSingleExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -2367,7 +2376,7 @@ class Session(_SessionClassMethods, EventTarget):
     @overload
     def scalars(
         self,
-        statement: TypedReturnsRows[Tuple[_T]],
+        statement: TypedReturnsRows[_T],
         params: Optional[_CoreAnyExecuteParams] = None,
         *,
         execution_options: OrmExecuteOptionsParameter = util.EMPTY_DICT,
@@ -2801,7 +2810,7 @@ class Session(_SessionClassMethods, EventTarget):
     @overload
     def query(
         self, _colexpr: TypedColumnsClauseRole[_T]
-    ) -> RowReturningQuery[Tuple[_T]]:
+    ) -> RowReturningQuery[_T]:
         ...
 
     # START OVERLOADED FUNCTIONS self.query RowReturningQuery 2-8
@@ -2812,13 +2821,13 @@ class Session(_SessionClassMethods, EventTarget):
     @overload
     def query(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1]]:
+    ) -> RowReturningQuery[_T0, _T1]:
         ...
 
     @overload
     def query(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2]:
         ...
 
     @overload
@@ -2829,7 +2838,7 @@ class Session(_SessionClassMethods, EventTarget):
         __ent2: _TCCA[_T2],
         __ent3: _TCCA[_T3],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3]:
         ...
 
     @overload
@@ -2841,7 +2850,7 @@ class Session(_SessionClassMethods, EventTarget):
         __ent3: _TCCA[_T3],
         __ent4: _TCCA[_T4],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4]:
         ...
 
     @overload
@@ -2854,7 +2863,7 @@ class Session(_SessionClassMethods, EventTarget):
         __ent4: _TCCA[_T4],
         __ent5: _TCCA[_T5],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5]:
         ...
 
     @overload
@@ -2868,7 +2877,7 @@ class Session(_SessionClassMethods, EventTarget):
         __ent5: _TCCA[_T5],
         __ent6: _TCCA[_T6],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+    ) -> RowReturningQuery[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
         ...
 
     @overload
@@ -2883,7 +2892,10 @@ class Session(_SessionClassMethods, EventTarget):
         __ent6: _TCCA[_T6],
         __ent7: _TCCA[_T7],
         /,
-    ) -> RowReturningQuery[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+        *entities: _ColumnsClauseArgument[Any],
+    ) -> RowReturningQuery[
+        _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+    ]:
         ...
 
     # END OVERLOADED FUNCTIONS self.query
@@ -3124,7 +3136,7 @@ class Session(_SessionClassMethods, EventTarget):
 
         with_for_update = ForUpdateArg._from_argument(with_for_update)
 
-        stmt: Select[Any] = sql.select(object_mapper(instance))
+        stmt: Select[Unpack[TupleAny]] = sql.select(object_mapper(instance))
         if (
             loading.load_on_ident(
                 self,
index 786b55e14951acf1f5303140aca13a7942a8d09a..234a028a1529f56a67e28db51248b1af68bcbeb1 100644 (file)
@@ -46,6 +46,8 @@ from .. import exc as sa_exc
 from .. import inspection
 from .. import util
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import _IdentityKeyType
@@ -93,7 +95,10 @@ class _InstallLoaderCallableProto(Protocol[_O]):
     """
 
     def __call__(
-        self, state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
+        self,
+        state: InstanceState[_O],
+        dict_: _InstanceDict,
+        row: Row[Unpack[TupleAny]],
     ) -> None:
         ...
 
@@ -673,7 +678,9 @@ class InstanceState(interfaces.InspectionAttrInfo, Generic[_O]):
             fixed_impl = impl
 
             def _set_callable(
-                state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
+                state: InstanceState[_O],
+                dict_: _InstanceDict,
+                row: Row[Unpack[TupleAny]],
             ) -> None:
                 if "callables" not in state.__dict__:
                     state.callables = {}
@@ -685,7 +692,9 @@ class InstanceState(interfaces.InspectionAttrInfo, Generic[_O]):
         else:
 
             def _set_callable(
-                state: InstanceState[_O], dict_: _InstanceDict, row: Row[Any]
+                state: InstanceState[_O],
+                dict_: _InstanceDict,
+                row: Row[Unpack[TupleAny]],
             ) -> None:
                 if "callables" not in state.__dict__:
                     state.callables = {}
index 89d5ad491ce2f50c5eae924b525d91fdea0d8a55..4309cb119e27afa1d3fbe2cbb21a96b427d023b4 100644 (file)
@@ -92,7 +92,9 @@ from ..util.typing import (
 from ..util.typing import eval_name_only as _eval_name_only
 from ..util.typing import is_origin_of_cls
 from ..util.typing import Literal
+from ..util.typing import TupleAny
 from ..util.typing import typing_get_origin
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from ._typing import _EntityType
@@ -426,7 +428,7 @@ def identity_key(
     ident: Union[Any, Tuple[Any, ...]] = None,
     *,
     instance: Optional[_T] = None,
-    row: Optional[Union[Row[Any], RowMapping]] = None,
+    row: Optional[Union[Row[Unpack[TupleAny]], RowMapping]] = None,
     identity_token: Optional[Any] = None,
 ) -> _IdentityKeyType[_T]:
     r"""Generate "identity key" tuples, as are used as keys in the
@@ -1721,10 +1723,10 @@ class Bundle(
 
     def create_row_processor(
         self,
-        query: Select[Any],
-        procs: Sequence[Callable[[Row[Any]], Any]],
+        query: Select[Unpack[TupleAny]],
+        procs: Sequence[Callable[[Row[Unpack[TupleAny]]], Any]],
         labels: Sequence[str],
-    ) -> Callable[[Row[Any]], Any]:
+    ) -> Callable[[Row[Unpack[TupleAny]]], Any]:
         """Produce the "row processing" function for this :class:`.Bundle`.
 
         May be overridden by subclasses to provide custom behaviors when
@@ -1760,7 +1762,7 @@ class Bundle(
         """
         keyed_tuple = result_tuple(labels, [() for l in labels])
 
-        def proc(row: Row[Any]) -> Any:
+        def proc(row: Row[Unpack[TupleAny]]) -> Any:
             return keyed_tuple([proc(row) for proc in procs])
 
         return proc
index 2f54fc9a86f45d55f5887260286bc0af7d15fb0e..3764a6bb5c2438a9dbf1b7f0974d18d18f0fbd28 100644 (file)
@@ -587,7 +587,7 @@ class WriteOnlyCollection(AbstractCollectionWriter[_T]):
             "produce a SQL statement and execute it with session.scalars()."
         )
 
-    def select(self) -> Select[Tuple[_T]]:
+    def select(self) -> Select[_T]:
         """Produce a :class:`_sql.Select` construct that represents the
         rows within this instance-local :class:`_orm.WriteOnlyCollection`.
 
index 77e36d04bb1602a6f4b6488cda91648ff00195f2..736b4961ecba10abd90881b78feeff7855767f99 100644 (file)
@@ -10,7 +10,6 @@ from __future__ import annotations
 from typing import Any
 from typing import Optional
 from typing import overload
-from typing import Tuple
 from typing import TYPE_CHECKING
 from typing import TypeVar
 from typing import Union
@@ -32,6 +31,8 @@ from .selectable import Select
 from .selectable import TableClause
 from .selectable import TableSample
 from .selectable import Values
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from ._typing import _FromClauseArgument
@@ -331,21 +332,19 @@ def outerjoin(
 
 
 @overload
-def select(__ent0: _TCCA[_T0], /) -> Select[Tuple[_T0]]:
+def select(__ent0: _TCCA[_T0], /) -> Select[_T0]:
     ...
 
 
 @overload
-def select(
-    __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-) -> Select[Tuple[_T0, _T1]]:
+def select(__ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /) -> Select[_T0, _T1]:
     ...
 
 
 @overload
 def select(
     __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-) -> Select[Tuple[_T0, _T1, _T2]]:
+) -> Select[_T0, _T1, _T2]:
     ...
 
 
@@ -356,7 +355,7 @@ def select(
     __ent2: _TCCA[_T2],
     __ent3: _TCCA[_T3],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3]]:
+) -> Select[_T0, _T1, _T2, _T3]:
     ...
 
 
@@ -368,7 +367,7 @@ def select(
     __ent3: _TCCA[_T3],
     __ent4: _TCCA[_T4],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+) -> Select[_T0, _T1, _T2, _T3, _T4]:
     ...
 
 
@@ -381,7 +380,7 @@ def select(
     __ent4: _TCCA[_T4],
     __ent5: _TCCA[_T5],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+) -> Select[_T0, _T1, _T2, _T3, _T4, _T5]:
     ...
 
 
@@ -395,7 +394,7 @@ def select(
     __ent5: _TCCA[_T5],
     __ent6: _TCCA[_T6],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
     ...
 
 
@@ -410,7 +409,7 @@ def select(
     __ent6: _TCCA[_T6],
     __ent7: _TCCA[_T7],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]:
     ...
 
 
@@ -426,7 +425,7 @@ def select(
     __ent7: _TCCA[_T7],
     __ent8: _TCCA[_T8],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8]]:
+) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8]:
     ...
 
 
@@ -443,7 +442,10 @@ def select(
     __ent8: _TCCA[_T8],
     __ent9: _TCCA[_T9],
     /,
-) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9]]:
+    *entities: _ColumnsClauseArgument[Any],
+) -> Select[
+    _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, Unpack[TupleAny]
+]:
     ...
 
 
@@ -451,11 +453,15 @@ def select(
 
 
 @overload
-def select(*entities: _ColumnsClauseArgument[Any], **__kw: Any) -> Select[Any]:
+def select(
+    *entities: _ColumnsClauseArgument[Any], **__kw: Any
+) -> Select[Unpack[TupleAny]]:
     ...
 
 
-def select(*entities: _ColumnsClauseArgument[Any], **__kw: Any) -> Select[Any]:
+def select(
+    *entities: _ColumnsClauseArgument[Any], **__kw: Any
+) -> Select[Unpack[TupleAny]]:
     r"""Construct a new :class:`_expression.Select`.
 
 
index 7c3e58b4bca575ae2282ef2782b5edda188b3856..689ed19a9f8ce55ace6516b666c139a563c19c94 100644 (file)
@@ -19,7 +19,6 @@ from typing import Optional
 from typing import overload
 from typing import Protocol
 from typing import Set
-from typing import Tuple
 from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
@@ -30,7 +29,9 @@ from .. import exc
 from .. import util
 from ..inspection import Inspectable
 from ..util.typing import Literal
+from ..util.typing import TupleAny
 from ..util.typing import TypeAlias
+from ..util.typing import Unpack
 
 if TYPE_CHECKING:
     from datetime import date
@@ -157,8 +158,6 @@ _TypedColumnClauseArgument = Union[
     Type[_T],
 ]
 
-_TP = TypeVar("_TP", bound=Tuple[Any, ...])
-
 _T0 = TypeVar("_T0", bound=Any)
 _T1 = TypeVar("_T1", bound=Any)
 _T2 = TypeVar("_T2", bound=Any)
@@ -329,7 +328,7 @@ if TYPE_CHECKING:
 
     def is_select_statement(
         t: Union[Executable, ReturnsRows]
-    ) -> TypeGuard[Select[Any]]:
+    ) -> TypeGuard[Select[Unpack[TupleAny]]]:
         ...
 
     def is_table(t: FromClause) -> TypeGuard[TableClause]:
index 2d6f33063162cb76cc50ca65d3bb54964e24266f..ea19e9a86dc6483fae93b0802b93ba79f9dfe621 100644 (file)
@@ -88,6 +88,8 @@ from .. import exc
 from .. import util
 from ..util import FastIntFlag
 from ..util.typing import Literal
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from .annotation import _AnnotationDict
@@ -405,7 +407,7 @@ class _CompilerStackEntry(_BaseCompilerStackEntry, total=False):
     need_result_map_for_nested: bool
     need_result_map_for_compound: bool
     select_0: ReturnsRows
-    insert_from_select: Select[Any]
+    insert_from_select: Select[Unpack[TupleAny]]
 
 
 class ExpandedState(NamedTuple):
@@ -4786,7 +4788,7 @@ class SQLCompiler(Compiled):
         return text
 
     def _setup_select_hints(
-        self, select: Select[Any]
+        self, select: Select[Unpack[TupleAny]]
     ) -> Tuple[str, _FromHintsType]:
         byfrom = {
             from_: hinttext
index 0e3f872988ea2c10993c1b1316c1c7a500343b3b..f35815ca4f7f5f5e1d278bc5228ce92c65cda286 100644 (file)
@@ -32,7 +32,6 @@ from typing import Union
 from . import coercions
 from . import roles
 from . import util as sql_util
-from ._typing import _TP
 from ._typing import _unexpected_kw
 from ._typing import is_column_element
 from ._typing import is_named_from_clause
@@ -66,7 +65,11 @@ from .visitors import InternalTraversal
 from .. import exc
 from .. import util
 from ..util.typing import Self
+from ..util.typing import TupleAny
 from ..util.typing import TypeGuard
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
+
 
 if TYPE_CHECKING:
     from ._typing import _ColumnExpressionArgument
@@ -107,6 +110,7 @@ else:
 
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
 
 _DMLColumnElement = Union[str, ColumnClause[Any]]
 _DMLTableElement = Union[TableClause, Alias, Join]
@@ -960,7 +964,7 @@ class ValuesBase(UpdateBase):
 
     _supports_multi_parameters = False
 
-    select: Optional[Select[Any]] = None
+    select: Optional[Select[Unpack[TupleAny]]] = None
     """SELECT statement for INSERT .. FROM SELECT"""
 
     _post_values_clause: Optional[ClauseElement] = None
@@ -1299,7 +1303,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0]]:
+        ) -> ReturningInsert[_T0]:
             ...
 
         @overload
@@ -1310,7 +1314,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1]]:
+        ) -> ReturningInsert[_T0, _T1]:
             ...
 
         @overload
@@ -1322,7 +1326,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2]]:
+        ) -> ReturningInsert[_T0, _T1, _T2]:
             ...
 
         @overload
@@ -1335,7 +1339,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3]]:
+        ) -> ReturningInsert[_T0, _T1, _T2, _T3]:
             ...
 
         @overload
@@ -1349,7 +1353,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+        ) -> ReturningInsert[_T0, _T1, _T2, _T3, _T4]:
             ...
 
         @overload
@@ -1364,7 +1368,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+        ) -> ReturningInsert[_T0, _T1, _T2, _T3, _T4, _T5]:
             ...
 
         @overload
@@ -1380,7 +1384,7 @@ class Insert(ValuesBase):
             /,
             *,
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+        ) -> ReturningInsert[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
             ...
 
         @overload
@@ -1395,9 +1399,11 @@ class Insert(ValuesBase):
             __ent6: _TCCA[_T6],
             __ent7: _TCCA[_T7],
             /,
-            *,
+            *entities: _ColumnsClauseArgument[Any],
             sort_by_parameter_order: bool = False,
-        ) -> ReturningInsert[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+        ) -> ReturningInsert[
+            _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+        ]:
             ...
 
         # END OVERLOADED FUNCTIONS self.returning
@@ -1420,7 +1426,7 @@ class Insert(ValuesBase):
             ...
 
 
-class ReturningInsert(Insert, TypedReturnsRows[_TP]):
+class ReturningInsert(Insert, TypedReturnsRows[Unpack[_Ts]]):
     """Typing-only class that establishes a generic type form of
     :class:`.Insert` which tracks returned column types.
 
@@ -1607,21 +1613,19 @@ class Update(DMLWhereBase, ValuesBase):
         # statically generated** by tools/generate_tuple_map_overloads.py
 
         @overload
-        def returning(
-            self, __ent0: _TCCA[_T0], /
-        ) -> ReturningUpdate[Tuple[_T0]]:
+        def returning(self, __ent0: _TCCA[_T0], /) -> ReturningUpdate[_T0]:
             ...
 
         @overload
         def returning(
             self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-        ) -> ReturningUpdate[Tuple[_T0, _T1]]:
+        ) -> ReturningUpdate[_T0, _T1]:
             ...
 
         @overload
         def returning(
             self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2]]:
+        ) -> ReturningUpdate[_T0, _T1, _T2]:
             ...
 
         @overload
@@ -1632,7 +1636,7 @@ class Update(DMLWhereBase, ValuesBase):
             __ent2: _TCCA[_T2],
             __ent3: _TCCA[_T3],
             /,
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3]]:
+        ) -> ReturningUpdate[_T0, _T1, _T2, _T3]:
             ...
 
         @overload
@@ -1644,7 +1648,7 @@ class Update(DMLWhereBase, ValuesBase):
             __ent3: _TCCA[_T3],
             __ent4: _TCCA[_T4],
             /,
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+        ) -> ReturningUpdate[_T0, _T1, _T2, _T3, _T4]:
             ...
 
         @overload
@@ -1657,7 +1661,7 @@ class Update(DMLWhereBase, ValuesBase):
             __ent4: _TCCA[_T4],
             __ent5: _TCCA[_T5],
             /,
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+        ) -> ReturningUpdate[_T0, _T1, _T2, _T3, _T4, _T5]:
             ...
 
         @overload
@@ -1671,7 +1675,7 @@ class Update(DMLWhereBase, ValuesBase):
             __ent5: _TCCA[_T5],
             __ent6: _TCCA[_T6],
             /,
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+        ) -> ReturningUpdate[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
             ...
 
         @overload
@@ -1686,7 +1690,10 @@ class Update(DMLWhereBase, ValuesBase):
             __ent6: _TCCA[_T6],
             __ent7: _TCCA[_T7],
             /,
-        ) -> ReturningUpdate[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+            *entities: _ColumnsClauseArgument[Any],
+        ) -> ReturningUpdate[
+            _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+        ]:
             ...
 
         # END OVERLOADED FUNCTIONS self.returning
@@ -1703,7 +1710,7 @@ class Update(DMLWhereBase, ValuesBase):
             ...
 
 
-class ReturningUpdate(Update, TypedReturnsRows[_TP]):
+class ReturningUpdate(Update, TypedReturnsRows[Unpack[_Ts]]):
     """Typing-only class that establishes a generic type form of
     :class:`.Update` which tracks returned column types.
 
@@ -1752,21 +1759,19 @@ class Delete(DMLWhereBase, UpdateBase):
         # statically generated** by tools/generate_tuple_map_overloads.py
 
         @overload
-        def returning(
-            self, __ent0: _TCCA[_T0], /
-        ) -> ReturningDelete[Tuple[_T0]]:
+        def returning(self, __ent0: _TCCA[_T0], /) -> ReturningDelete[_T0]:
             ...
 
         @overload
         def returning(
             self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /
-        ) -> ReturningDelete[Tuple[_T0, _T1]]:
+        ) -> ReturningDelete[_T0, _T1]:
             ...
 
         @overload
         def returning(
             self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2]]:
+        ) -> ReturningDelete[_T0, _T1, _T2]:
             ...
 
         @overload
@@ -1777,7 +1782,7 @@ class Delete(DMLWhereBase, UpdateBase):
             __ent2: _TCCA[_T2],
             __ent3: _TCCA[_T3],
             /,
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3]]:
+        ) -> ReturningDelete[_T0, _T1, _T2, _T3]:
             ...
 
         @overload
@@ -1789,7 +1794,7 @@ class Delete(DMLWhereBase, UpdateBase):
             __ent3: _TCCA[_T3],
             __ent4: _TCCA[_T4],
             /,
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+        ) -> ReturningDelete[_T0, _T1, _T2, _T3, _T4]:
             ...
 
         @overload
@@ -1802,7 +1807,7 @@ class Delete(DMLWhereBase, UpdateBase):
             __ent4: _TCCA[_T4],
             __ent5: _TCCA[_T5],
             /,
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+        ) -> ReturningDelete[_T0, _T1, _T2, _T3, _T4, _T5]:
             ...
 
         @overload
@@ -1816,7 +1821,7 @@ class Delete(DMLWhereBase, UpdateBase):
             __ent5: _TCCA[_T5],
             __ent6: _TCCA[_T6],
             /,
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+        ) -> ReturningDelete[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
             ...
 
         @overload
@@ -1831,7 +1836,10 @@ class Delete(DMLWhereBase, UpdateBase):
             __ent6: _TCCA[_T6],
             __ent7: _TCCA[_T7],
             /,
-        ) -> ReturningDelete[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+            *entities: _ColumnsClauseArgument[Any],
+        ) -> ReturningDelete[
+            _T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, Unpack[TupleAny]
+        ]:
             ...
 
         # END OVERLOADED FUNCTIONS self.returning
@@ -1839,16 +1847,16 @@ class Delete(DMLWhereBase, UpdateBase):
         @overload
         def returning(
             self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
-        ) -> ReturningDelete[Any]:
+        ) -> ReturningDelete[Unpack[TupleAny]]:
             ...
 
         def returning(
             self, *cols: _ColumnsClauseArgument[Any], **__kw: Any
-        ) -> ReturningDelete[Any]:
+        ) -> ReturningDelete[Unpack[TupleAny]]:
             ...
 
 
-class ReturningDelete(Update, TypedReturnsRows[_TP]):
+class ReturningDelete(Update, TypedReturnsRows[Unpack[_Ts]]):
     """Typing-only class that establishes a generic type form of
     :class:`.Delete` which tracks returned column types.
 
index 8f48e78ed0ffe7b13855e22b763abbd982615478..973b332d47490f334303123d1911700dbc2c0965 100644 (file)
@@ -78,6 +78,8 @@ from ..util import HasMemoized_ro_memoized_attribute
 from ..util import TypingOnly
 from ..util.typing import Literal
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from ._typing import _ByArgument
@@ -510,7 +512,7 @@ class ClauseElement(
         connection: Connection,
         distilled_params: _CoreMultiExecuteParams,
         execution_options: CoreExecuteOptionsParameter,
-    ) -> Result[Any]:
+    ) -> Result[Unpack[TupleAny]]:
         if self.supports_execution:
             if TYPE_CHECKING:
                 assert isinstance(self, Executable)
@@ -2144,12 +2146,10 @@ class BindParameter(roles.InElementRole, KeyedColumnElement[_T]):
                 else:
                     check_value = value
                 cast(
-                    "BindParameter[typing_Tuple[Any, ...]]", self
+                    "BindParameter[TupleAny]", self
                 ).type = type_._resolve_values_to_types(check_value)
             else:
-                cast(
-                    "BindParameter[typing_Tuple[Any, ...]]", self
-                ).type = type_
+                cast("BindParameter[TupleAny]", self).type = type_
         else:
             self.type = type_
 
@@ -3277,7 +3277,7 @@ and_ = BooleanClauseList.and_
 or_ = BooleanClauseList.or_
 
 
-class Tuple(ClauseList, ColumnElement[typing_Tuple[Any, ...]]):
+class Tuple(ClauseList, ColumnElement[TupleAny]):
     """Represent a SQL tuple."""
 
     __visit_name__ = "tuple"
index 5cb5812d692431eab9417d45d43fa0ca9930dc68..fd38c78d28dc34cac75b44e27bfb3f20571c8dfc 100644 (file)
@@ -702,7 +702,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
             joins_implicitly=joins_implicitly,
         )
 
-    def select(self) -> Select[Tuple[_T]]:
+    def select(self) -> Select[_T]:
         """Produce a :func:`_expression.select` construct
         against this :class:`.FunctionElement`.
 
@@ -711,7 +711,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
             s = select(function_element)
 
         """
-        s: Select[Any] = Select(self)
+        s: Select[_T] = Select(self)
         if self._execution_options:
             s = s.execution_options(**self._execution_options)
         return s
index 7e3c7150cfa3ed56793dd5d82b92c2ead6fd42aa..ae52e5db45d181aedcd66505fa29679789f47c46 100644 (file)
@@ -48,7 +48,6 @@ from . import type_api
 from . import visitors
 from ._typing import _ColumnsClauseArgument
 from ._typing import _no_kw
-from ._typing import _TP
 from ._typing import is_column_element
 from ._typing import is_select_statement
 from ._typing import is_subquery
@@ -100,10 +99,16 @@ from .. import util
 from ..util import HasMemoized_ro_memoized_attribute
 from ..util.typing import Literal
 from ..util.typing import Self
+from ..util.typing import TupleAny
+from ..util.typing import TypeVarTuple
+from ..util.typing import Unpack
+
 
 and_ = BooleanClauseList.and_
 
 _T = TypeVar("_T", bound=Any)
+_Ts = TypeVarTuple("_Ts")
+
 
 if TYPE_CHECKING:
     from ._typing import _ColumnExpressionArgument
@@ -283,7 +288,7 @@ class ExecutableReturnsRows(Executable, ReturnsRows):
     """base for executable statements that return rows."""
 
 
-class TypedReturnsRows(ExecutableReturnsRows, Generic[_TP]):
+class TypedReturnsRows(ExecutableReturnsRows, Generic[Unpack[_Ts]]):
     """base for executable statements that return rows."""
 
 
@@ -610,7 +615,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable):
 
     _use_schema_map = False
 
-    def select(self) -> Select[Any]:
+    def select(self) -> Select[Unpack[TupleAny]]:
         r"""Return a SELECT of this :class:`_expression.FromClause`.
 
 
@@ -1496,7 +1501,7 @@ class Join(roles.DMLTableRole, FromClause):
                 "join explicitly." % (a.description, b.description)
             )
 
-    def select(self) -> Select[Any]:
+    def select(self) -> Select[Unpack[TupleAny]]:
         r"""Create a :class:`_expression.Select` from this
         :class:`_expression.Join`.
 
@@ -2052,7 +2057,7 @@ class CTE(
 
     def _init(
         self,
-        selectable: Select[Any],
+        selectable: Select[Unpack[TupleAny]],
         *,
         name: Optional[str] = None,
         recursive: bool = False,
@@ -3477,7 +3482,7 @@ class SelectBase(
         "first in order to create "
         "a subquery, which then can be selected.",
     )
-    def select(self, *arg: Any, **kw: Any) -> Select[Any]:
+    def select(self, *arg: Any, **kw: Any) -> Select[Unpack[TupleAny]]:
         return self._implicit_subquery.select(*arg, **kw)
 
     @HasMemoized.memoized_attribute
@@ -4492,7 +4497,7 @@ class SelectState(util.MemoizedSlots, CompileState):
 
     def __init__(
         self,
-        statement: Select[Any],
+        statement: Select[Unpack[TupleAny]],
         compiler: Optional[SQLCompiler],
         **kw: Any,
     ):
@@ -4520,7 +4525,7 @@ class SelectState(util.MemoizedSlots, CompileState):
 
     @classmethod
     def get_column_descriptions(
-        cls, statement: Select[Any]
+        cls, statement: Select[Unpack[TupleAny]]
     ) -> List[Dict[str, Any]]:
         return [
             {
@@ -4535,13 +4540,15 @@ class SelectState(util.MemoizedSlots, CompileState):
 
     @classmethod
     def from_statement(
-        cls, statement: Select[Any], from_statement: roles.ReturnsRowsRole
+        cls,
+        statement: Select[Unpack[TupleAny]],
+        from_statement: roles.ReturnsRowsRole,
     ) -> ExecutableReturnsRows:
         cls._plugin_not_implemented()
 
     @classmethod
     def get_columns_clause_froms(
-        cls, statement: Select[Any]
+        cls, statement: Select[Unpack[TupleAny]]
     ) -> List[FromClause]:
         return cls._normalize_froms(
             itertools.chain.from_iterable(
@@ -4596,7 +4603,9 @@ class SelectState(util.MemoizedSlots, CompileState):
 
         return go
 
-    def _get_froms(self, statement: Select[Any]) -> List[FromClause]:
+    def _get_froms(
+        self, statement: Select[Unpack[TupleAny]]
+    ) -> List[FromClause]:
         ambiguous_table_name_map: _AmbiguousTableNameMap
         self._ambiguous_table_name_map = ambiguous_table_name_map = {}
 
@@ -4624,7 +4633,7 @@ class SelectState(util.MemoizedSlots, CompileState):
     def _normalize_froms(
         cls,
         iterable_of_froms: Iterable[FromClause],
-        check_statement: Optional[Select[Any]] = None,
+        check_statement: Optional[Select[Unpack[TupleAny]]] = None,
         ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] = None,
     ) -> List[FromClause]:
         """given an iterable of things to select FROM, reduce them to what
@@ -4769,7 +4778,7 @@ class SelectState(util.MemoizedSlots, CompileState):
 
     @classmethod
     def determine_last_joined_entity(
-        cls, stmt: Select[Any]
+        cls, stmt: Select[Unpack[TupleAny]]
     ) -> Optional[_JoinTargetElement]:
         if stmt._setup_joins:
             return stmt._setup_joins[-1][0]
@@ -4777,7 +4786,9 @@ class SelectState(util.MemoizedSlots, CompileState):
             return None
 
     @classmethod
-    def all_selected_columns(cls, statement: Select[Any]) -> _SelectIterable:
+    def all_selected_columns(
+        cls, statement: Select[Unpack[TupleAny]]
+    ) -> _SelectIterable:
         return [c for c in _select_iterables(statement._raw_columns)]
 
     def _setup_joins(
@@ -5023,7 +5034,9 @@ class _MemoizedSelectEntities(
         return c
 
     @classmethod
-    def _generate_for_statement(cls, select_stmt: Select[Any]) -> None:
+    def _generate_for_statement(
+        cls, select_stmt: Select[Unpack[TupleAny]]
+    ) -> None:
         if select_stmt._setup_joins or select_stmt._with_options:
             self = _MemoizedSelectEntities()
             self._raw_columns = select_stmt._raw_columns
@@ -5042,7 +5055,7 @@ class Select(
     HasCompileState,
     _SelectFromElements,
     GenerativeSelect,
-    TypedReturnsRows[_TP],
+    TypedReturnsRows[Unpack[_Ts]],
 ):
     """Represents a ``SELECT`` statement.
 
@@ -5116,7 +5129,7 @@ class Select(
     _compile_state_factory: Type[SelectState]
 
     @classmethod
-    def _create_raw_select(cls, **kw: Any) -> Select[Any]:
+    def _create_raw_select(cls, **kw: Any) -> Select[Unpack[TupleAny]]:
         """Create a :class:`.Select` using raw ``__new__`` with no coercions.
 
         Used internally to build up :class:`.Select` constructs with
@@ -5178,13 +5191,13 @@ class Select(
 
         @overload
         def scalar_subquery(
-            self: Select[Tuple[_MAYBE_ENTITY]],
+            self: Select[_MAYBE_ENTITY],
         ) -> ScalarSelect[Any]:
             ...
 
         @overload
         def scalar_subquery(
-            self: Select[Tuple[_NOT_ENTITY]],
+            self: Select[_NOT_ENTITY],
         ) -> ScalarSelect[_NOT_ENTITY]:
             ...
 
@@ -5666,7 +5679,7 @@ class Select(
     @_generative
     def add_columns(
         self, *entities: _ColumnsClauseArgument[Any]
-    ) -> Select[Any]:
+    ) -> Select[Unpack[TupleAny]]:
         r"""Return a new :func:`_expression.select` construct with
         the given entities appended to its columns clause.
 
@@ -5716,7 +5729,9 @@ class Select(
         "be removed in a future release.  Please use "
         ":meth:`_expression.Select.add_columns`",
     )
-    def column(self, column: _ColumnsClauseArgument[Any]) -> Select[Any]:
+    def column(
+        self, column: _ColumnsClauseArgument[Any]
+    ) -> Select[Unpack[TupleAny]]:
         """Return a new :func:`_expression.select` construct with
         the given column expression added to its columns clause.
 
@@ -5733,7 +5748,9 @@ class Select(
         return self.add_columns(column)
 
     @util.preload_module("sqlalchemy.sql.util")
-    def reduce_columns(self, only_synonyms: bool = True) -> Select[Any]:
+    def reduce_columns(
+        self, only_synonyms: bool = True
+    ) -> Select[Unpack[TupleAny]]:
         """Return a new :func:`_expression.select` construct with redundantly
         named, equivalently-valued columns removed from the columns clause.
 
@@ -5756,7 +5773,7 @@ class Select(
          all columns that are equivalent to another are removed.
 
         """
-        woc: Select[Any]
+        woc: Select[Unpack[TupleAny]]
         woc = self.with_only_columns(
             *util.preloaded.sql_util.reduce_columns(
                 self._all_selected_columns,
@@ -5772,19 +5789,19 @@ class Select(
     # statically generated** by tools/generate_sel_v1_overloads.py
 
     @overload
-    def with_only_columns(self, __ent0: _TCCA[_T0]) -> Select[Tuple[_T0]]:
+    def with_only_columns(self, __ent0: _TCCA[_T0]) -> Select[_T0]:
         ...
 
     @overload
     def with_only_columns(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
-    ) -> Select[Tuple[_T0, _T1]]:
+    ) -> Select[_T0, _T1]:
         ...
 
     @overload
     def with_only_columns(
         self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
-    ) -> Select[Tuple[_T0, _T1, _T2]]:
+    ) -> Select[_T0, _T1, _T2]:
         ...
 
     @overload
@@ -5794,7 +5811,7 @@ class Select(
         __ent1: _TCCA[_T1],
         __ent2: _TCCA[_T2],
         __ent3: _TCCA[_T3],
-    ) -> Select[Tuple[_T0, _T1, _T2, _T3]]:
+    ) -> Select[_T0, _T1, _T2, _T3]:
         ...
 
     @overload
@@ -5805,7 +5822,7 @@ class Select(
         __ent2: _TCCA[_T2],
         __ent3: _TCCA[_T3],
         __ent4: _TCCA[_T4],
-    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]:
+    ) -> Select[_T0, _T1, _T2, _T3, _T4]:
         ...
 
     @overload
@@ -5817,7 +5834,7 @@ class Select(
         __ent3: _TCCA[_T3],
         __ent4: _TCCA[_T4],
         __ent5: _TCCA[_T5],
-    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]:
+    ) -> Select[_T0, _T1, _T2, _T3, _T4, _T5]:
         ...
 
     @overload
@@ -5830,7 +5847,7 @@ class Select(
         __ent4: _TCCA[_T4],
         __ent5: _TCCA[_T5],
         __ent6: _TCCA[_T6],
-    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]:
+    ) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6]:
         ...
 
     @overload
@@ -5844,7 +5861,7 @@ class Select(
         __ent5: _TCCA[_T5],
         __ent6: _TCCA[_T6],
         __ent7: _TCCA[_T7],
-    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]:
+    ) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]:
         ...
 
     # END OVERLOADED FUNCTIONS self.with_only_columns
@@ -5855,7 +5872,7 @@ class Select(
         *entities: _ColumnsClauseArgument[Any],
         maintain_column_froms: bool = False,
         **__kw: Any,
-    ) -> Select[Any]:
+    ) -> Select[Unpack[TupleAny]]:
         ...
 
     @_generative
@@ -5864,7 +5881,7 @@ class Select(
         *entities: _ColumnsClauseArgument[Any],
         maintain_column_froms: bool = False,
         **__kw: Any,
-    ) -> Select[Any]:
+    ) -> Select[Unpack[TupleAny]]:
         r"""Return a new :func:`_expression.select` construct with its columns
         clause replaced with the given entities.
 
@@ -6257,7 +6274,7 @@ class Select(
         meth = SelectState.get_plugin_class(self).all_selected_columns
         return list(meth(self))
 
-    def _ensure_disambiguated_names(self) -> Select[Any]:
+    def _ensure_disambiguated_names(self) -> Select[Unpack[TupleAny]]:
         if self._label_style is LABEL_STYLE_NONE:
             self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
         return self
@@ -6517,7 +6534,9 @@ class ScalarSelect(
         by this :class:`_expression.ScalarSelect`.
 
         """
-        self.element = cast("Select[Any]", self.element).where(crit)
+        self.element = cast("Select[Unpack[TupleAny]]", self.element).where(
+            crit
+        )
         return self
 
     @overload
@@ -6539,7 +6558,7 @@ class ScalarSelect(
 
     if TYPE_CHECKING:
 
-        def _ungroup(self) -> Select[Any]:
+        def _ungroup(self) -> Select[Unpack[TupleAny]]:
             ...
 
     @_generative
@@ -6573,9 +6592,9 @@ class ScalarSelect(
 
 
         """
-        self.element = cast("Select[Any]", self.element).correlate(
-            *fromclauses
-        )
+        self.element = cast(
+            "Select[Unpack[TupleAny]]", self.element
+        ).correlate(*fromclauses)
         return self
 
     @_generative
@@ -6611,9 +6630,9 @@ class ScalarSelect(
 
         """
 
-        self.element = cast("Select[Any]", self.element).correlate_except(
-            *fromclauses
-        )
+        self.element = cast(
+            "Select[Unpack[TupleAny]]", self.element
+        ).correlate_except(*fromclauses)
         return self
 
 
@@ -6628,7 +6647,10 @@ class Exists(UnaryExpression[bool]):
     """
 
     inherit_cache = True
-    element: Union[SelectStatementGrouping[Select[Any]], ScalarSelect[Any]]
+    element: Union[
+        SelectStatementGrouping[Select[Unpack[TupleAny]]],
+        ScalarSelect[Any],
+    ]
 
     def __init__(
         self,
@@ -6663,8 +6685,9 @@ class Exists(UnaryExpression[bool]):
         return []
 
     def _regroup(
-        self, fn: Callable[[Select[Any]], Select[Any]]
-    ) -> SelectStatementGrouping[Select[Any]]:
+        self,
+        fn: Callable[[Select[Unpack[TupleAny]]], Select[Unpack[TupleAny]]],
+    ) -> SelectStatementGrouping[Select[Unpack[TupleAny]]]:
         element = self.element._ungroup()
         new_element = fn(element)
 
@@ -6672,7 +6695,7 @@ class Exists(UnaryExpression[bool]):
         assert isinstance(return_value, SelectStatementGrouping)
         return return_value
 
-    def select(self) -> Select[Any]:
+    def select(self) -> Select[Unpack[TupleAny]]:
         r"""Return a SELECT of this :class:`_expression.Exists`.
 
         e.g.::
index 0963e8ed200244a2ce41b22a14a7cb8ba2c597f8..a9e0084995c202668876468a96e3251e244ce8e5 100644 (file)
@@ -61,6 +61,7 @@ from ..util import langhelpers
 from ..util import OrderedDict
 from ..util.typing import is_literal
 from ..util.typing import Literal
+from ..util.typing import TupleAny
 from ..util.typing import typing_get_args
 
 if TYPE_CHECKING:
@@ -3156,7 +3157,7 @@ class ARRAY(
             )
 
 
-class TupleType(TypeEngine[Tuple[Any, ...]]):
+class TupleType(TypeEngine[TupleAny]):
     """represent the composite type of a Tuple."""
 
     _is_tuple_type = True
index ac8b30eb3174d38f411d5281bf4ba3dc290e1c2f..53e5726722bcd4d2c1e35a7ae4af0d022817b6f9 100644 (file)
@@ -70,6 +70,7 @@ from .visitors import _ET
 from .. import exc
 from .. import util
 from ..util.typing import Literal
+from ..util.typing import Unpack
 
 if typing.TYPE_CHECKING:
     from ._typing import _EquivalentColumnMap
@@ -588,7 +589,9 @@ class _repr_row(_repr_base):
 
     __slots__ = ("row",)
 
-    def __init__(self, row: Row[Any], max_chars: int = 300):
+    def __init__(
+        self, row: Row[Unpack[Tuple[Any, ...]]], max_chars: int = 300
+    ):
         self.row = row
         self.max_chars = max_chars
 
index ce3aa9fe321b2260c492012d2ed6a59a70fba6e1..a3e9397640297f66fed142e413c155184deb6ba6 100644 (file)
@@ -49,8 +49,11 @@ if True:  # zimports removes the tailing comments
     from typing_extensions import ParamSpec as ParamSpec  # 3.10
     from typing_extensions import TypeAlias as TypeAlias  # 3.10
     from typing_extensions import TypeGuard as TypeGuard  # 3.10
+    from typing_extensions import TypeVarTuple as TypeVarTuple  # 3.11
     from typing_extensions import Self as Self  # 3.11
     from typing_extensions import TypeAliasType as TypeAliasType  # 3.12
+    from typing_extensions import Unpack as Unpack  # 3.11
+
 
 _T = TypeVar("_T", bound=Any)
 _KT = TypeVar("_KT")
@@ -59,6 +62,8 @@ _KT_contra = TypeVar("_KT_contra", contravariant=True)
 _VT = TypeVar("_VT")
 _VT_co = TypeVar("_VT_co", covariant=True)
 
+TupleAny = Tuple[Any, ...]
+
 
 if compat.py310:
     # why they took until py310 to put this in stdlib is beyond me,
index 78117e32280347973211aad233386e9b72325c12..3bbd1b8788df25768e6737f47a639c8285607246 100644 (file)
@@ -7,6 +7,7 @@ from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_false
 from sqlalchemy.testing import is_true
+from sqlalchemy.testing.assertions import expect_deprecated
 from sqlalchemy.testing.assertions import expect_raises
 from sqlalchemy.testing.util import picklers
 from sqlalchemy.util import compat
@@ -331,6 +332,7 @@ class ResultTest(fixtures.TestBase):
         eq_(m1.fetchone(), {"a": 1, "b": 1, "c": 1})
         eq_(r1.fetchone(), (2, 1, 2))
 
+    @expect_deprecated(".*is deprecated, Row now behaves like a tuple.*")
     def test_tuples_plus_base(self):
         r1 = self._fixture()
 
index a06406c11542a5d478734c86aa908efeb8b5a25e..1e2b36810755dea08afe40823f0203ae4eaf3a80 100644 (file)
@@ -75,6 +75,7 @@ from sqlalchemy.testing.assertions import assert_raises
 from sqlalchemy.testing.assertions import assert_raises_message
 from sqlalchemy.testing.assertions import assert_warns_message
 from sqlalchemy.testing.assertions import eq_
+from sqlalchemy.testing.assertions import expect_deprecated
 from sqlalchemy.testing.assertions import expect_raises
 from sqlalchemy.testing.assertions import expect_warnings
 from sqlalchemy.testing.assertions import is_not_none
@@ -188,6 +189,7 @@ class OnlyReturnTuplesTest(QueryTest):
         assert isinstance(row, collections_abc.Sequence)
         assert isinstance(row._mapping, collections_abc.Mapping)
 
+    @expect_deprecated(".*is deprecated, Row now behaves like a tuple.*")
     def test_single_entity_tuples(self):
         User = self.classes.User
         query = fixture_session().query(User).tuples()
@@ -214,6 +216,7 @@ class OnlyReturnTuplesTest(QueryTest):
         assert isinstance(row, collections_abc.Sequence)
         assert isinstance(row._mapping, collections_abc.Mapping)
 
+    @expect_deprecated(".*is deprecated, Row now behaves like a tuple.*")
     def test_multiple_entity_true_tuples(self):
         User = self.classes.User
         query = fixture_session().query(User.id, User).tuples()
index 2caef7f8e545f756fcf779b3173ba476691170b0..1848f7bdd379112dbb7ddad59183861e730e7272 100644 (file)
@@ -50,6 +50,7 @@ from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import assertions
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_deprecated
 from sqlalchemy.testing import expect_raises
 from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import fixtures
@@ -269,6 +270,7 @@ class CursorResultTest(fixtures.TablesTest):
         r = connection.scalars(users.select().order_by(users.c.user_id))
         eq_(r.all(), [7, 8, 9])
 
+    @expect_deprecated(".*is deprecated, Row now behaves like a tuple.*")
     def test_result_tuples(self, connection):
         users = self.tables.users
 
@@ -285,6 +287,7 @@ class CursorResultTest(fixtures.TablesTest):
         ).tuples()
         eq_(r.all(), [(7, "jack"), (8, "ed"), (9, "fred")])
 
+    @expect_deprecated(".*is deprecated, Row now behaves like a tuple.*")
     def test_row_tuple(self, connection):
         users = self.tables.users
 
index 5777b91484180aaa6784f53485c9b2729fb0f7e0..7d56c51a5bb7023c9106820f98b2451a45257bd3 100644 (file)
@@ -15,7 +15,7 @@ def regular() -> None:
 
         result = conn.execute(text("select * from table"))
 
-        # EXPECTED_TYPE: CursorResult[Any]
+        # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(result)
 
     with e.begin() as conn:
@@ -24,7 +24,7 @@ def regular() -> None:
 
         result = conn.execute(text("select * from table"))
 
-        # EXPECTED_TYPE: CursorResult[Any]
+        # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(result)
 
     engine = create_engine("postgresql://scott:tiger@localhost/test")
index 598d319a7765ef17db5d0360369df1b8717739cf..ae7880f58490cc0427aa3744970709a86d1d60e3 100644 (file)
@@ -14,20 +14,20 @@ async def asyncio() -> None:
 
         result = await conn.execute(text("select * from table"))
 
-        # EXPECTED_TYPE: CursorResult[Any]
+        # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(result)
 
         # stream with direct await
         async_result = await conn.stream(text("select * from table"))
 
-        # EXPECTED_TYPE: AsyncResult[Any]
+        # EXPECTED_TYPE: AsyncResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(async_result)
 
         # stream with context manager
         async with conn.stream(
             text("select * from table")
         ) as ctx_async_result:
-            # EXPECTED_TYPE: AsyncResult[Any]
+            # EXPECTED_TYPE: AsyncResult[Unpack[.*tuple[Any, ...]]]
             reveal_type(ctx_async_result)
 
         # stream_scalars with direct await
@@ -51,5 +51,5 @@ async def asyncio() -> None:
 
         result = await conn.execute(text("select * from table"))
 
-        # EXPECTED_TYPE: CursorResult[Any]
+        # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(result)
index 52a2a19ed0cee04be85d23e1fef6b244cf43348a..aef41395fee4b85d72cd2dda2286b84b4c9a1b8c 100644 (file)
@@ -87,5 +87,5 @@ if typing.TYPE_CHECKING:
     # EXPECTED_TYPE: SQLCoreOperations[bool]
     reveal_type(expr4)
 
-    # EXPECTED_TYPE: Select[Tuple[bool]]
+    # EXPECTED_TYPE: Select[bool]
     reveal_type(stmt2)
index 8ac1f504c26b4bc809dea1811cb61b4b9cc50f0e..f82bbe7c2dfd7d40d992cd11ac2b85af42dd50e3 100644 (file)
@@ -58,7 +58,7 @@ v1 = Vertex(start=Point(3, 4), end=Point(5, 6))
 
 stmt = select(Vertex).where(Vertex.start.in_([Point(3, 4)]))
 
-# EXPECTED_TYPE: Select[Tuple[Vertex]]
+# EXPECTED_TYPE: Select[Vertex]
 reveal_type(stmt)
 
 # EXPECTED_TYPE: composite.Point
index fa1b16a2a67ee0b6d463cae36782951bd7ea6a9d..3d8117a999a79e5a7edc1110687c7f8437f5ee93 100644 (file)
@@ -38,7 +38,7 @@ v1 = Vertex(start=Point(3, 4), end=Point(5, 6))
 
 stmt = select(Vertex).where(Vertex.start.in_([Point(3, 4)]))
 
-# EXPECTED_TYPE: Select[Tuple[Vertex]]
+# EXPECTED_TYPE: Select[Vertex]
 reveal_type(stmt)
 
 # EXPECTED_TYPE: composite.Point
index fc304db87e97ca0cf852a21cf23075eb865f9e7c..79f1548e36504653181da2ccd6ced4fc11266054 100644 (file)
@@ -74,7 +74,7 @@ class Manager(Employee):
 def do_something_with_mapped_class(
     cls_: MappedClassProtocol[Employee],
 ) -> None:
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(cls_.__table__.select())
 
     # EXPECTED_TYPE: Mapper[Employee]
index 72dc72df1ecd668fefc859be48020420aa5f4b92..20bc424ce24975443d79edb94bc540fd30d8470d 100644 (file)
@@ -40,7 +40,7 @@ def get_messages() -> Sequence[Message]:
         message_query = select(Message)
 
         if TYPE_CHECKING:
-            # EXPECTED_TYPE: Select[Tuple[Message]]
+            # EXPECTED_TYPE: Select[Message]
             reveal_type(message_query)
 
         return session.scalars(message_query).all()
@@ -57,7 +57,7 @@ def get_poly_messages() -> Sequence[Message]:
         poly_query = select(PolymorphicMessage)
 
         if TYPE_CHECKING:
-            # EXPECTED_TYPE: Select[Tuple[Message]]
+            # EXPECTED_TYPE: Select[Message]
             reveal_type(poly_query)
 
         return session.scalars(poly_query).all()
index 0f1c35eafa13598e12b15e982388bbaf13088cbc..12a261a84f7cb7afdffb23dacbfe926ce1134014 100644 (file)
@@ -61,7 +61,7 @@ with Session(e) as sess:
     q2 = sess.query(User.id).filter_by(id=7)
     rows2 = q2.all()
 
-    # EXPECTED_TYPE: List[Row[Tuple[int]]]
+    # EXPECTED_TYPE: List[.*Row[.*int].*]
     reveal_type(rows2)
 
     # test #8280
@@ -86,7 +86,7 @@ with Session(e) as sess:
     # test #9125
 
     for row in sess.query(User.id, User.name):
-        # EXPECTED_TYPE: Row[Tuple[int, str]]
+        # EXPECTED_TYPE: .*Row[int, str].*
         reveal_type(row)
 
     for uobj1 in sess.query(User):
index 7d8a2dd1a3283f8572f2b2051dd844d752c3e4e0..47168f474bb1e1c6e317d3af827f95f812b62bc3 100644 (file)
@@ -53,12 +53,12 @@ connection = e.connect()
 def t_select_1() -> None:
     stmt = select(User.id, User.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: Select[Tuple[int, str]]
+    # EXPECTED_TYPE: Select[int, str]
     reveal_type(stmt)
 
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Tuple[int, str]]
+    # EXPECTED_TYPE: .*Result[int, str].*
     reveal_type(result)
 
 
@@ -77,12 +77,12 @@ def t_select_2() -> None:
         .fetch(User.id)
     )
 
-    # EXPECTED_TYPE: Select[Tuple[User]]
+    # EXPECTED_TYPE: Select[User]
     reveal_type(stmt)
 
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Tuple[User]]
+    # EXPECTED_TYPE: .*Result[User].*
     reveal_type(result)
 
 
@@ -102,12 +102,12 @@ def t_select_3() -> None:
 
     stmt = select(ua.id, ua.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: Select[Tuple[int, str]]
+    # EXPECTED_TYPE: Select[int, str]
     reveal_type(stmt)
 
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Tuple[int, str]]
+    # EXPECTED_TYPE: .*Result[int, str].*
     reveal_type(result)
 
 
@@ -115,12 +115,12 @@ def t_select_4() -> None:
     ua = aliased(User)
     stmt = select(ua, User).filter(User.id == 5)
 
-    # EXPECTED_TYPE: Select[Tuple[User, User]]
+    # EXPECTED_TYPE: Select[User, User]
     reveal_type(stmt)
 
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Tuple[User, User]]
+    # EXPECTED_TYPE: Result[User, User]
     reveal_type(result)
 
 
@@ -137,7 +137,7 @@ def t_legacy_query_single_entity() -> None:
     reveal_type(q1.all())
 
     # mypy switches to builtins.list for some reason here
-    # EXPECTED_RE_TYPE: .*\.[Ll]ist\[.*Row\*?\[Tuple\[.*User\]\]\]
+    # EXPECTED_RE_TYPE: .*\.[Ll]ist\[.*Row\*?\[.*User\].*\]
     reveal_type(q1.only_return_tuples(True).all())
 
     # EXPECTED_TYPE: List[Tuple[User]]
@@ -147,15 +147,15 @@ def t_legacy_query_single_entity() -> None:
 def t_legacy_query_cols_1() -> None:
     q1 = session.query(User.id, User.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: RowReturningQuery[Tuple[int, str]]
+    # EXPECTED_TYPE: RowReturningQuery[int, str]
     reveal_type(q1)
 
-    # EXPECTED_TYPE: Row[Tuple[int, str]]
+    # EXPECTED_TYPE: .*Row[int, str].*
     reveal_type(q1.one())
 
     r1 = q1.one()
 
-    x, y = r1.t
+    x, y = r1
 
     # EXPECTED_TYPE: int
     reveal_type(x)
@@ -167,7 +167,7 @@ def t_legacy_query_cols_1() -> None:
 def t_legacy_query_cols_tupleq_1() -> None:
     q1 = session.query(User.id, User.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: RowReturningQuery[Tuple[int, str]]
+    # EXPECTED_TYPE: RowReturningQuery[int, str]
     reveal_type(q1)
 
     q2 = q1.tuples()
@@ -194,15 +194,15 @@ def t_legacy_query_cols_1_with_entities() -> None:
 
     q2 = q1.with_entities(User.id, User.name)
 
-    # EXPECTED_TYPE: RowReturningQuery[Tuple[int, str]]
+    # EXPECTED_TYPE: RowReturningQuery[int, str]
     reveal_type(q2)
 
-    # EXPECTED_TYPE: Row[Tuple[int, str]]
+    # EXPECTED_TYPE: .*Row[int, str].*
     reveal_type(q2.one())
 
     r1 = q2.one()
 
-    x, y = r1.t
+    x, y = r1
 
     # EXPECTED_TYPE: int
     reveal_type(x)
@@ -214,20 +214,20 @@ def t_legacy_query_cols_1_with_entities() -> None:
 def t_select_with_only_cols() -> None:
     q1 = select(User).where(User.id == 5)
 
-    # EXPECTED_TYPE: Select[Tuple[User]]
+    # EXPECTED_TYPE: Select[User]
     reveal_type(q1)
 
     q2 = q1.with_only_columns(User.id, User.name)
 
-    # EXPECTED_TYPE: Select[Tuple[int, str]]
+    # EXPECTED_TYPE: Select[int, str]
     reveal_type(q2)
 
     row = connection.execute(q2).one()
 
-    # EXPECTED_TYPE: Row[Tuple[int, str]]
+    # EXPECTED_TYPE: .*Row[int, str].*
     reveal_type(row)
 
-    x, y = row.t
+    x, y = row
 
     # EXPECTED_TYPE: int
     reveal_type(x)
@@ -240,15 +240,15 @@ def t_legacy_query_cols_2() -> None:
     a1 = aliased(User)
     q1 = session.query(User, a1, User.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: RowReturningQuery[Tuple[User, User, str]]
+    # EXPECTED_TYPE: RowReturningQuery[User, User, str]
     reveal_type(q1)
 
-    # EXPECTED_TYPE: Row[Tuple[User, User, str]]
+    # EXPECTED_TYPE: .*Row[User, User, str].*
     reveal_type(q1.one())
 
     r1 = q1.one()
 
-    x, y, z = r1.t
+    x, y, z = r1
 
     # EXPECTED_TYPE: User
     reveal_type(x)
@@ -269,15 +269,15 @@ def t_legacy_query_cols_2_with_entities() -> None:
     a1 = aliased(User)
     q2 = q1.with_entities(User, a1, User.name).filter(User.id == 5)
 
-    # EXPECTED_TYPE: RowReturningQuery[Tuple[User, User, str]]
+    # EXPECTED_TYPE: RowReturningQuery[User, User, str]
     reveal_type(q2)
 
-    # EXPECTED_TYPE: Row[Tuple[User, User, str]]
+    # EXPECTED_TYPE: .*Row[User, User, str].*
     reveal_type(q2.one())
 
     r1 = q2.one()
 
-    x, y, z = r1.t
+    x, y, z = r1
 
     # EXPECTED_TYPE: User
     reveal_type(x)
@@ -295,7 +295,7 @@ def t_select_add_col_loses_type() -> None:
     q2 = q1.add_columns(User.data)
 
     # note this should not match Select
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(q2)
 
 
@@ -388,7 +388,7 @@ def t_select_w_core_selectables() -> None:
     # mypy would downgrade to Any rather than picking the basemost type.
     # with typing integrated into Select etc. we can at least get a Select
     # object back.
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(s2)
 
     # so a fully explicit type may be given
@@ -400,7 +400,7 @@ def t_select_w_core_selectables() -> None:
     # plain FromClause etc we at least get Select
     s3 = select(s1)
 
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(s3)
 
     t1 = User.__table__
@@ -411,7 +411,7 @@ def t_select_w_core_selectables() -> None:
 
     s4 = select(t1)
 
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(s4)
 
 
@@ -420,31 +420,31 @@ def t_dml_insert() -> None:
 
     r1 = session.execute(s1)
 
-    # EXPECTED_TYPE: Result[Tuple[int, str]]
+    # EXPECTED_TYPE: Result[int, str]
     reveal_type(r1)
 
     s2 = insert(User).returning(User)
 
     r2 = session.execute(s2)
 
-    # EXPECTED_TYPE: Result[Tuple[User]]
+    # EXPECTED_TYPE: Result[User]
     reveal_type(r2)
 
     s3 = insert(User).returning(func.foo(), column("q"))
 
-    # EXPECTED_TYPE: ReturningInsert[Any]
+    # EXPECTED_TYPE: ReturningInsert[Unpack[.*tuple[Any, ...]]]
     reveal_type(s3)
 
     r3 = session.execute(s3)
 
-    # EXPECTED_TYPE: Result[Any]
+    # EXPECTED_TYPE: Result[Unpack[.*tuple[Any, ...]]]
     reveal_type(r3)
 
 
 def t_dml_bare_insert() -> None:
     s1 = insert(User)
     r1 = session.execute(s1)
-    # EXPECTED_TYPE: CursorResult[Any]
+    # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
     reveal_type(r1)
     # EXPECTED_TYPE: int
     reveal_type(r1.rowcount)
@@ -453,7 +453,7 @@ def t_dml_bare_insert() -> None:
 def t_dml_bare_update() -> None:
     s1 = update(User)
     r1 = session.execute(s1)
-    # EXPECTED_TYPE: CursorResult[Any]
+    # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
     reveal_type(r1)
     # EXPECTED_TYPE: int
     reveal_type(r1.rowcount)
@@ -462,7 +462,7 @@ def t_dml_bare_update() -> None:
 def t_dml_update_with_values() -> None:
     s1 = update(User).values({User.id: 123, User.data: "value"})
     r1 = session.execute(s1)
-    # EXPECTED_TYPE: CursorResult[Any]
+    # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
     reveal_type(r1)
     # EXPECTED_TYPE: int
     reveal_type(r1.rowcount)
@@ -471,7 +471,7 @@ def t_dml_update_with_values() -> None:
 def t_dml_bare_delete() -> None:
     s1 = delete(User)
     r1 = session.execute(s1)
-    # EXPECTED_TYPE: CursorResult[Any]
+    # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
     reveal_type(r1)
     # EXPECTED_TYPE: int
     reveal_type(r1.rowcount)
@@ -482,7 +482,7 @@ def t_dml_update() -> None:
 
     r1 = session.execute(s1)
 
-    # EXPECTED_TYPE: Result[Tuple[int, str]]
+    # EXPECTED_TYPE: Result[int, str]
     reveal_type(r1)
 
 
@@ -491,7 +491,7 @@ def t_dml_delete() -> None:
 
     r1 = session.execute(s1)
 
-    # EXPECTED_TYPE: Result[Tuple[int, str]]
+    # EXPECTED_TYPE: Result[int, str]
     reveal_type(r1)
 
 
index 57aae8fac81895b673c082d2a49023857ce5d80d..730d99bc1512470178d29003d90842e9f40a4d08 100644 (file)
@@ -66,7 +66,7 @@ reveal_type(e1)
 
 stmt = select(e1)
 
-# EXPECTED_TYPE: Select[Tuple[bool]]
+# EXPECTED_TYPE: Select[bool]
 reveal_type(stmt)
 
 stmt = stmt.where(e1)
@@ -79,7 +79,7 @@ reveal_type(e2)
 
 stmt = select(e2)
 
-# EXPECTED_TYPE: Select[Tuple[bool]]
+# EXPECTED_TYPE: Select[bool]
 reveal_type(stmt)
 
 stmt = stmt.where(e2)
@@ -89,14 +89,14 @@ stmt2 = select(User.id).order_by("id", "email").group_by("email", "id")
 stmt2 = (
     select(User.id).order_by(asc("id"), desc("email")).group_by("email", "id")
 )
-# EXPECTED_TYPE: Select[Tuple[int]]
+# EXPECTED_TYPE: Select[int]
 reveal_type(stmt2)
 
 stmt2 = select(User.id).order_by(User.id).group_by(User.email)
 stmt2 = (
     select(User.id).order_by(User.id, User.email).group_by(User.email, User.id)
 )
-# EXPECTED_TYPE: Select[Tuple[int]]
+# EXPECTED_TYPE: Select[int]
 reveal_type(stmt2)
 
 
@@ -118,7 +118,7 @@ receives_bool_col_expr(user_table.c.email == "x")
 
 q1 = Session().query(User.id).order_by("email").group_by("email")
 q1 = Session().query(User.id).order_by("id", "email").group_by("email", "id")
-# EXPECTED_TYPE: RowReturningQuery[Tuple[int]]
+# EXPECTED_TYPE: RowReturningQuery[int]
 reveal_type(q1)
 
 q1 = Session().query(User.id).order_by(User.id).group_by(User.email)
@@ -128,7 +128,7 @@ q1 = (
     .order_by(User.id, User.email)
     .group_by(User.email, User.id)
 )
-# EXPECTED_TYPE: RowReturningQuery[Tuple[int]]
+# EXPECTED_TYPE: RowReturningQuery[int]
 reveal_type(q1)
 
 # test 9174
index 6a345fcf6ec6463e753e7ab0e347db0661422876..726c24b3f1d6645653268149d59cbadc6e86d4c3 100644 (file)
@@ -14,139 +14,139 @@ from sqlalchemy import String
 
 stmt1 = select(func.aggregate_strings(column("x", String), ","))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt1)
 
 
 stmt2 = select(func.char_length(column("x")))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt2)
 
 
 stmt3 = select(func.coalesce(column("x", Integer)))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt3)
 
 
 stmt4 = select(func.concat())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt4)
 
 
 stmt5 = select(func.count(column("x")))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt5)
 
 
 stmt6 = select(func.cume_dist())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*Decimal\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*Decimal\]
 reveal_type(stmt6)
 
 
 stmt7 = select(func.current_date())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*date\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*date\]
 reveal_type(stmt7)
 
 
 stmt8 = select(func.current_time())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*time\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*time\]
 reveal_type(stmt8)
 
 
 stmt9 = select(func.current_timestamp())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*datetime\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*datetime\]
 reveal_type(stmt9)
 
 
 stmt10 = select(func.current_user())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt10)
 
 
 stmt11 = select(func.dense_rank())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt11)
 
 
 stmt12 = select(func.localtime())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*datetime\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*datetime\]
 reveal_type(stmt12)
 
 
 stmt13 = select(func.localtimestamp())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*datetime\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*datetime\]
 reveal_type(stmt13)
 
 
 stmt14 = select(func.max(column("x", Integer)))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt14)
 
 
 stmt15 = select(func.min(column("x", Integer)))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt15)
 
 
 stmt16 = select(func.next_value(Sequence("x_seq")))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt16)
 
 
 stmt17 = select(func.now())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*datetime\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*datetime\]
 reveal_type(stmt17)
 
 
 stmt18 = select(func.percent_rank())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*Decimal\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*Decimal\]
 reveal_type(stmt18)
 
 
 stmt19 = select(func.rank())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt19)
 
 
 stmt20 = select(func.session_user())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt20)
 
 
 stmt21 = select(func.sum(column("x", Integer)))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt21)
 
 
 stmt22 = select(func.sysdate())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*datetime\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*datetime\]
 reveal_type(stmt22)
 
 
 stmt23 = select(func.user())
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt23)
 
 # END GENERATED FUNCTION TYPING TESTS
index da656f2d1d98395787902cbb560b6406739fa87d..b5a12f24d65738082268c36a79118d177e69f14a 100644 (file)
@@ -40,7 +40,7 @@ stmt1 = select(
     Foo.a,
     func.min(Foo.b),
 ).group_by(Foo.a)
-# EXPECTED_TYPE: Select[Tuple[int, int]]
+# EXPECTED_TYPE: Select[int, int]
 reveal_type(stmt1)
 
 # test #10818
@@ -52,5 +52,5 @@ stmt2 = select(
     Foo.a,
     func.coalesce(Foo.c, "a", "b"),
 ).group_by(Foo.a)
-# EXPECTED_TYPE: Select[Tuple[int, str]]
+# EXPECTED_TYPE: Select[int, str]
 reveal_type(stmt2)
index bce5557db8d6209aa3674b264be3f3d84061bd32..035fde800d5d0da612c0bf70a3ec478d62702ef8 100644 (file)
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-from typing import Tuple
 from typing import TYPE_CHECKING
 
 from sqlalchemy import Column
@@ -62,15 +61,15 @@ with e.connect() as conn:
     result = conn.execute(s6)
 
     if TYPE_CHECKING:
-        # EXPECTED_TYPE: CursorResult[Any]
+        # EXPECTED_TYPE: CursorResult[Unpack[.*tuple[Any, ...]]]
         reveal_type(result)
 
     # we can type these like this
-    my_result: Result[Tuple[User]] = conn.execute(s6)
+    my_result: Result[User] = conn.execute(s6)
 
     if TYPE_CHECKING:
         # pyright and mypy disagree on the specific type here,
         # mypy sees Result as we said, pyright seems to upgrade it to
         # CursorResult
-        # EXPECTED_RE_TYPE: .*(?:Cursor)?Result\[Tuple\[.*User\]\]
+        # EXPECTED_RE_TYPE: .*(?:Cursor)?Result\[.*User\]
         reveal_type(my_result)
index c7842a7e7995d0407d46b1b94c73a67b92e6ada2..3c8b7f91348e60a007202ddd798de9717b3b184f 100644 (file)
@@ -3,7 +3,6 @@ from __future__ import annotations
 import asyncio
 from typing import cast
 from typing import Optional
-from typing import Tuple
 from typing import Type
 
 from sqlalchemy import Column
@@ -87,18 +86,18 @@ reveal_type(async_session)
 
 single_stmt = select(User.name).where(User.name == "foo")
 
-# EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[Tuple\[builtins.str\*?\]\]
+# EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[builtins.str\*?\]
 reveal_type(single_stmt)
 
 multi_stmt = select(User.id, User.name).where(User.name == "foo")
 
-# EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+# EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[builtins.int\*?, builtins.str\*?\]
 reveal_type(multi_stmt)
 
 
 def t_result_ctxmanager() -> None:
     with connection.execute(select(column("q", Integer))) as r1:
-        # EXPECTED_TYPE: CursorResult[Tuple[int]]
+        # EXPECTED_TYPE: CursorResult[int]
         reveal_type(r1)
 
         with r1.mappings() as r1m:
@@ -110,7 +109,7 @@ def t_result_ctxmanager() -> None:
         reveal_type(r2)
 
     with session.execute(select(User.id)) as r3:
-        # EXPECTED_TYPE: Result[Tuple[int]]
+        # EXPECTED_TYPE: Result[int]
         reveal_type(r3)
 
     with session.scalars(select(User.id)) as r4:
@@ -130,14 +129,14 @@ def t_entity_varieties() -> None:
 
     r1 = session.execute(s1)
 
-    # EXPECTED_RE_TYPE: sqlalchemy..*.Result\[Tuple\[builtins.int\*?, typed_results.User\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy..*.Result\[builtins.int\*?, typed_results.User\*?, builtins.str\*?\]
     reveal_type(r1)
 
     s2 = select(User, a1).where(User.name == "foo")
 
     r2 = session.execute(s2)
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*Result\[Tuple\[typed_results.User\*?, typed_results.User\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*Result\[typed_results.User\*?, typed_results.User\*?\]
     reveal_type(r2)
 
     row = r2.t.one()
@@ -153,18 +152,18 @@ def t_entity_varieties() -> None:
     # automatically typed since they are dynamically generated
     a1_id = cast(Mapped[int], a1.id)
     s3 = select(User.id, a1_id, a1, User).where(User.name == "foo")
-    # EXPECTED_RE_TYPE: sqlalchemy.*Select\*?\[Tuple\[builtins.int\*?, builtins.int\*?, typed_results.User\*?, typed_results.User\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*Select\*?\[builtins.int\*?, builtins.int\*?, typed_results.User\*?, typed_results.User\*?\]
     reveal_type(s3)
 
     # testing Mapped[entity]
     some_mp = cast(Mapped[User], object())
     s4 = select(some_mp, a1, User).where(User.name == "foo")
 
-    # NOTEXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[Tuple\[typed_results.User\*?, typed_results.User\*?, typed_results.User\*?\]\]
+    # NOTEXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[typed_results.User\*?, typed_results.User\*?, typed_results.User\*?\]
 
-    # sqlalchemy.sql._gen_overloads.Select[Tuple[typed_results.User, typed_results.User, typed_results.User]]
+    # sqlalchemy.sql._gen_overloads.Select[typed_results.User, typed_results.User, typed_results.User]
 
-    # EXPECTED_TYPE: Select[Tuple[User, User, User]]
+    # EXPECTED_TYPE: Select[User, User, User]
     reveal_type(s4)
 
     # test plain core expressions
@@ -173,30 +172,30 @@ def t_entity_varieties() -> None:
 
     s5 = select(x, y, User.name + "hi")
 
-    # EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[Tuple\[builtins.int\*?, builtins.int\*?\, builtins.str\*?]\]
+    # EXPECTED_RE_TYPE: sqlalchemy..*Select\*?\[builtins.int\*?, builtins.int\*?\, builtins.str\*?]
     reveal_type(s5)
 
 
 def t_ambiguous_result_type_one() -> None:
     stmt = select(column("q", Integer), table("x", column("y")))
 
-    # EXPECTED_TYPE: Select[Any]
+    # EXPECTED_TYPE: Select[Unpack[.*tuple[Any, ...]]]
     reveal_type(stmt)
 
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Any]
+    # EXPECTED_TYPE: Result[Unpack[.*tuple[Any, ...]]]
     reveal_type(result)
 
 
 def t_ambiguous_result_type_two() -> None:
     stmt = select(column("q"))
 
-    # EXPECTED_TYPE: Select[Tuple[Any]]
+    # EXPECTED_TYPE: Select[Any]
     reveal_type(stmt)
     result = session.execute(stmt)
 
-    # EXPECTED_TYPE: Result[Any]
+    # EXPECTED_TYPE: Result[Unpack[.*tuple[Any, ...]]]
     reveal_type(result)
 
 
@@ -204,11 +203,11 @@ def t_aliased() -> None:
     a1 = aliased(User)
 
     s1 = select(a1)
-    # EXPECTED_TYPE: Select[Tuple[User]]
+    # EXPECTED_TYPE: Select[User]
     reveal_type(s1)
 
     s4 = select(a1.name, a1, a1, User).where(User.name == "foo")
-    # EXPECTED_TYPE: Select[Tuple[str, User, User, User]]
+    # EXPECTED_TYPE: Select[str, User, User, User]
     reveal_type(s4)
 
 
@@ -341,11 +340,11 @@ async def t_async_result_insertmanyvalues_scalars() -> None:
 def t_connection_execute_multi_row_t() -> None:
     result = connection.execute(multi_stmt)
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*CursorResult\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: sqlalchemy.*CursorResult\[builtins.int\*?, builtins.str\*?\]
     reveal_type(result)
     row = result.one()
 
-    # EXPECTED_RE_TYPE: sqlalchemy.*Row\[Tuple\[builtins.int\*?, builtins.str\*?\]\]
+    # EXPECTED_RE_TYPE: .*sqlalchemy.*Row\[builtins.int\*?, builtins.str\*?\].*
     reveal_type(row)
 
     x, y = row.t
@@ -681,18 +680,18 @@ def test_outerjoin_10173() -> None:
         id: Mapped[int] = mapped_column(primary_key=True)
         name: Mapped[str]
 
-    stmt: Select[Tuple[User, Other]] = select(User, Other).outerjoin(
+    stmt: Select[User, Other] = select(User, Other).outerjoin(
         Other, User.id == Other.id
     )
-    stmt2: Select[Tuple[User, Optional[Other]]] = select(
+    stmt2: Select[User, Optional[Other]] = select(
         User, Nullable(Other)
     ).outerjoin(Other, User.id == Other.id)
-    stmt3: Select[Tuple[int, Optional[str]]] = select(
+    stmt3: Select[int, Optional[str]] = select(
         User.id, Nullable(Other.name)
     ).outerjoin(Other, User.id == Other.id)
 
     def go(W: Optional[Type[Other]]) -> None:
-        stmt4: Select[Tuple[str, Other]] = select(
+        stmt4: Select[str, Other] = select(
             NotNullable(User.value), NotNullable(W)
         ).where(User.value.is_not(None))
         print(stmt4)
index 51422dc7e6b835a514246b5ee78c7eb4d05605f5..411cfed7219943b38473753c64084f4315138b8b 100644 (file)
@@ -169,7 +169,7 @@ def {key}(self) -> Type[{_type}]:{_reserved_word}
                                 rf"""
 stmt{count} = select(func.{key}(column('x', Integer)))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*int\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*int\]
 reveal_type(stmt{count})
 
 """,
@@ -183,7 +183,7 @@ reveal_type(stmt{count})
                                 rf"""
 stmt{count} = select(func.{key}(column('x', String), ','))
 
-# EXPECTED_RE_TYPE: .*Select\[Tuple\[.*str\]\]
+# EXPECTED_RE_TYPE: .*Select\[.*str\]
 reveal_type(stmt{count})
 
 """,
@@ -195,7 +195,7 @@ reveal_type(stmt{count})
                         fn_class.type, TypeEngine
                     ):
                         python_type = fn_class.type.python_type
-                        python_expr = rf"Tuple\[.*{python_type.__name__}\]"
+                        python_expr = rf".*{python_type.__name__}"
                         argspec = inspect.getfullargspec(fn_class)
                         if fn_class.__name__ == "next_value":
                             args = "Sequence('x_seq')"
index e886b7fddcc42c73f5e8f687818f7ab8527ca11f..9ca648333cde1d8608f079df5f39da8a30a74315 100644 (file)
@@ -82,17 +82,26 @@ def process_module(modname: str, filename: str, cmd: code_writer_cmd) -> str:
                 )
 
                 for num_args in range(start_index, end_index + 1):
+                    ret_suffix = ""
                     combinations = [
                         f"__ent{arg}: _TCCA[_T{arg}]"
                         for arg in range(num_args)
                     ]
+
+                    if num_args == end_index:
+                        ret_suffix = ", Unpack[TupleAny]"
+                        extra_args = (
+                            f", *entities: _ColumnsClauseArgument[Any]"
+                            f"{extra_args.replace(', *', '')}"
+                        )
+
                     buf.write(
                         textwrap.indent(
                             f"""
 @overload
 def {current_fnname}(
     {'self, ' if use_self else ''}{", ".join(combinations)},/{extra_args}
-) -> {return_type}[Tuple[{', '.join(f'_T{i}' for i in range(num_args))}]]:
+) -> {return_type}[{', '.join(f'_T{i}' for i in range(num_args))}{ret_suffix}]:
     ...
 
 """,  # noqa: E501
diff --git a/tox.ini b/tox.ini
index cd07aa9620259ec8be3eabd8b326e578b47ac23f..dbffc9e206d215a0e488424c3d46a008c8b4a1ce 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -180,7 +180,7 @@ commands=
 [testenv:pep484]
 deps=
      greenlet != 0.4.17
-     mypy >= 1.6.0
+     mypy >= 1.7.0
      types-greenlet
 commands =
     mypy  {env:MYPY_COLOR} ./lib/sqlalchemy
@@ -193,7 +193,7 @@ deps=
      pytest>=7.0.0rc1,<8
      pytest-xdist
      greenlet != 0.4.17
-     mypy >= 1.2.0
+     mypy >= 1.7.0
      patch==1.*
      types-greenlet
 commands =