1# engine/result.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""Define generic result set constructs."""
9
10from __future__ import annotations
11
12import functools
13import itertools
14import operator
15import typing
16from typing import Any
17from typing import Callable
18from typing import cast
19from typing import Dict
20from typing import Generic
21from typing import Iterable
22from typing import Iterator
23from typing import List
24from typing import Literal
25from typing import Mapping
26from typing import NoReturn
27from typing import Optional
28from typing import overload
29from typing import Sequence
30from typing import Tuple
31from typing import TYPE_CHECKING
32from typing import Union
33
34from ._result_cy import _InterimRowType
35from ._result_cy import _NO_ROW as _NO_ROW
36from ._result_cy import _R as _R
37from ._result_cy import _RowData
38from ._result_cy import _T
39from ._result_cy import _UniqueFilterType as _UniqueFilterType
40from ._result_cy import BaseResultInternal
41from ._util_cy import tuplegetter as tuplegetter
42from .row import Row
43from .row import RowMapping
44from .. import exc
45from .. import util
46from ..sql.base import _generative
47from ..sql.base import InPlaceGenerative
48from ..util import deprecated
49from ..util import NONE_SET
50from ..util.typing import Never
51from ..util.typing import Self
52from ..util.typing import TupleAny
53from ..util.typing import TypeVarTuple
54from ..util.typing import Unpack
55
56if typing.TYPE_CHECKING:
57 from typing import Type
58
59 from .. import inspection
60 from ..sql import roles
61 from ..sql._typing import _HasClauseElement
62 from ..sql.elements import SQLCoreOperations
63 from ..sql.type_api import _ResultProcessorType
64
65_KeyType = Union[
66 str,
67 "SQLCoreOperations[Any]",
68 "roles.TypedColumnsClauseRole[Any]",
69 "roles.ColumnsClauseRole",
70 "Type[Any]",
71 "inspection.Inspectable[_HasClauseElement[Any]]",
72]
73_KeyIndexType = Union[_KeyType, int]
74
75# is overridden in cursor using _CursorKeyMapRecType
76_KeyMapRecType = Any
77
78_KeyMapType = Mapping[_KeyType, _KeyMapRecType]
79
80
81_Ts = TypeVarTuple("_Ts")
82
83
84_InterimSupportsScalarsRowType = Union[Row[Unpack[TupleAny]], Any]
85
86_ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
87_TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
88
89
90class ResultMetaData:
91 """Base for metadata about result rows."""
92
93 __slots__ = ()
94
95 _tuplefilter: Optional[_TupleGetterType] = None
96 _translated_indexes: Optional[Sequence[int]] = None
97 _create_unique_filters: Optional[
98 Callable[["Result[Any]"], Sequence[Optional[Callable[[Any], Any]]]]
99 ] = None
100 _keymap: _KeyMapType
101 _keys: Sequence[str]
102 _processors: Optional[_ProcessorsType]
103 _key_to_index: Dict[_KeyType, int]
104
105 @property
106 def keys(self) -> RMKeyView:
107 return RMKeyView(self)
108
109 def _has_key(self, key: object) -> bool:
110 raise NotImplementedError()
111
112 def _for_freeze(self) -> ResultMetaData:
113 raise NotImplementedError()
114
115 @overload
116 def _key_fallback(
117 self, key: Any, err: Optional[Exception], raiseerr: Literal[True] = ...
118 ) -> NoReturn: ...
119
120 @overload
121 def _key_fallback(
122 self,
123 key: Any,
124 err: Optional[Exception],
125 raiseerr: Literal[False] = ...,
126 ) -> None: ...
127
128 @overload
129 def _key_fallback(
130 self, key: Any, err: Optional[Exception], raiseerr: bool = ...
131 ) -> Optional[NoReturn]: ...
132
133 def _key_fallback(
134 self, key: Any, err: Optional[Exception], raiseerr: bool = True
135 ) -> Optional[NoReturn]:
136 assert raiseerr
137 raise KeyError(key) from err
138
139 def _raise_for_ambiguous_column_name(
140 self, rec: _KeyMapRecType
141 ) -> NoReturn:
142 raise NotImplementedError(
143 "ambiguous column name logic is implemented for "
144 "CursorResultMetaData"
145 )
146
147 def _index_for_key(
148 self, key: _KeyIndexType, raiseerr: bool
149 ) -> Optional[int]:
150 raise NotImplementedError()
151
152 def _indexes_for_keys(
153 self, keys: Sequence[_KeyIndexType]
154 ) -> Sequence[int]:
155 raise NotImplementedError()
156
157 def _metadata_for_keys(
158 self, keys: Sequence[_KeyIndexType]
159 ) -> Iterator[_KeyMapRecType]:
160 raise NotImplementedError()
161
162 def _reduce(self, keys: Sequence[_KeyIndexType]) -> ResultMetaData:
163 raise NotImplementedError()
164
165 def _getter(
166 self, key: Any, raiseerr: bool = True
167 ) -> Optional[Callable[[Row[Unpack[TupleAny]]], Any]]:
168 index = self._index_for_key(key, raiseerr)
169
170 if index is not None:
171 return operator.itemgetter(index)
172 else:
173 return None
174
175 def _row_as_tuple_getter(
176 self, keys: Sequence[_KeyIndexType]
177 ) -> _TupleGetterType:
178 indexes = self._indexes_for_keys(keys)
179 return tuplegetter(*indexes)
180
181 def _make_key_to_index(
182 self, keymap: Mapping[_KeyType, Sequence[Any]], index: int
183 ) -> Dict[_KeyType, int]:
184 return {
185 key: rec[index]
186 for key, rec in keymap.items()
187 if rec[index] is not None
188 }
189
190 def _key_not_found(self, key: Any, attr_error: bool) -> NoReturn:
191 if key in self._keymap:
192 # the index must be none in this case
193 self._raise_for_ambiguous_column_name(self._keymap[key])
194 else:
195 # unknown key
196 if attr_error:
197 try:
198 self._key_fallback(key, None)
199 except KeyError as ke:
200 raise AttributeError(ke.args[0]) from ke
201 else:
202 self._key_fallback(key, None)
203
204 @property
205 def _effective_processors(self) -> Optional[_ProcessorsType]:
206 if not self._processors or NONE_SET.issuperset(self._processors):
207 return None
208 else:
209 return self._processors
210
211
212class RMKeyView(typing.KeysView[Any]):
213 __slots__ = ("_parent", "_keys")
214
215 _parent: ResultMetaData
216 _keys: Sequence[str]
217
218 def __init__(self, parent: ResultMetaData):
219 self._parent = parent
220 self._keys = [k for k in parent._keys if k is not None]
221
222 def __len__(self) -> int:
223 return len(self._keys)
224
225 def __repr__(self) -> str:
226 return "{0.__class__.__name__}({0._keys!r})".format(self)
227
228 def __iter__(self) -> Iterator[str]:
229 return iter(self._keys)
230
231 def __contains__(self, item: Any) -> bool:
232 if isinstance(item, int):
233 return False
234
235 # note this also includes special key fallback behaviors
236 # which also don't seem to be tested in test_resultset right now
237 return self._parent._has_key(item)
238
239 def __eq__(self, other: Any) -> bool:
240 return list(other) == list(self)
241
242 def __ne__(self, other: Any) -> bool:
243 return list(other) != list(self)
244
245
246class SimpleResultMetaData(ResultMetaData):
247 """result metadata for in-memory collections."""
248
249 __slots__ = (
250 "_keys",
251 "_keymap",
252 "_processors",
253 "_tuplefilter",
254 "_translated_indexes",
255 "_create_unique_filters",
256 "_key_to_index",
257 "_ambiguous_keys",
258 )
259
260 _keys: Sequence[str]
261
262 def __init__(
263 self,
264 keys: Sequence[str],
265 extra: Optional[Sequence[Any]] = None,
266 _processors: Optional[_ProcessorsType] = None,
267 _tuplefilter: Optional[_TupleGetterType] = None,
268 _translated_indexes: Optional[Sequence[int]] = None,
269 _create_unique_filters: Optional[
270 Callable[
271 [Any],
272 Sequence[Optional[Callable[[Any], Any]]],
273 ]
274 ] = None,
275 _ambiguous_keys: Optional[frozenset[str]] = None,
276 ):
277 self._keys = list(keys)
278 self._tuplefilter = _tuplefilter
279 self._translated_indexes = _translated_indexes
280 self._create_unique_filters = _create_unique_filters
281 if extra:
282 assert len(self._keys) == len(extra)
283 recs_names = [
284 (
285 (name,) + (extras if extras else ()),
286 (index, name, extras),
287 )
288 for index, (name, extras) in enumerate(zip(self._keys, extra))
289 ]
290 else:
291 recs_names = [
292 ((name,), (index, name, ()))
293 for index, name in enumerate(self._keys)
294 ]
295
296 self._keymap = {key: rec for keys, rec in recs_names for key in keys}
297
298 if _ambiguous_keys:
299 for name in _ambiguous_keys.intersection(self._keymap):
300 rec = self._keymap[name]
301 self._keymap[name] = (None,) + rec[1:]
302
303 self._processors = _processors
304
305 self._ambiguous_keys = _ambiguous_keys
306
307 self._key_to_index = self._make_key_to_index(self._keymap, 0)
308
309 def _has_key(self, key: object) -> bool:
310 return key in self._keymap
311
312 def _for_freeze(self) -> ResultMetaData:
313 # TODO: are we freezing the result with or without uniqueness
314 # applied?
315 create_unique_filters = self._create_unique_filters
316 if create_unique_filters is not None and self._tuplefilter is not None:
317 _tuplefilter = self._tuplefilter
318 _orig_create_unique_filters = create_unique_filters
319
320 def create_unique_filters_filtered(
321 result: Result[Any],
322 ) -> Sequence[Optional[Callable[[Any], Any]]]:
323 return _tuplefilter(_orig_create_unique_filters(result))
324
325 create_unique_filters = create_unique_filters_filtered
326
327 return SimpleResultMetaData(
328 self._keys,
329 extra=[self._keymap[key][2] for key in self._keys],
330 _create_unique_filters=create_unique_filters,
331 _ambiguous_keys=self._ambiguous_keys,
332 )
333
334 def __getstate__(self) -> Dict[str, Any]:
335 return {
336 "_keys": self._keys,
337 "_translated_indexes": self._translated_indexes,
338 "_ambiguous_keys": self._ambiguous_keys,
339 }
340
341 def __setstate__(self, state: Dict[str, Any]) -> None:
342 if state["_translated_indexes"]:
343 _translated_indexes = state["_translated_indexes"]
344 _tuplefilter = tuplegetter(*_translated_indexes)
345 else:
346 _translated_indexes = _tuplefilter = None
347 self.__init__( # type: ignore
348 state["_keys"],
349 _translated_indexes=_translated_indexes,
350 _tuplefilter=_tuplefilter,
351 _ambiguous_keys=state.get("_ambiguous_keys"),
352 )
353
354 def _index_for_key(self, key: Any, raiseerr: bool = True) -> int:
355 if isinstance(key, int):
356 key = self._keys[key]
357 try:
358 rec = self._keymap[key]
359 except KeyError as ke:
360 rec = self._key_fallback(key, ke, raiseerr)
361
362 if rec[0] is None:
363 self._raise_for_ambiguous_column_name(rec)
364 return rec[0] # type: ignore[no-any-return]
365
366 def _raise_for_ambiguous_column_name(
367 self, rec: _KeyMapRecType
368 ) -> NoReturn:
369 raise exc.InvalidRequestError(
370 "Ambiguous column name '%s' in "
371 "result set column descriptions" % rec[1]
372 )
373
374 def _indexes_for_keys(self, keys: Sequence[Any]) -> Sequence[int]:
375 # only used by the ORM with Column objects; does not need
376 # ambiguous column name support
377 return [self._keymap[key][0] for key in keys]
378
379 def _metadata_for_keys(
380 self, keys: Sequence[Any]
381 ) -> Iterator[_KeyMapRecType]:
382 for key in keys:
383 if isinstance(key, int):
384 key = self._keys[key]
385
386 try:
387 rec = self._keymap[key]
388 except KeyError as ke:
389 rec = self._key_fallback(key, ke, True)
390
391 if rec[0] is None:
392 self._raise_for_ambiguous_column_name(rec)
393
394 yield rec
395
396 def _reduce(self, keys: Sequence[Any]) -> ResultMetaData:
397 try:
398 metadata_for_keys = [
399 self._keymap[self._keys[key] if isinstance(key, int) else key]
400 for key in keys
401 ]
402 except KeyError as ke:
403 self._key_fallback(ke.args[0], ke, True)
404
405 indexes: Sequence[int]
406 new_keys: Sequence[str]
407 extra: Sequence[Any]
408 indexes, new_keys, extra = zip(*metadata_for_keys)
409
410 if self._translated_indexes:
411 indexes = [self._translated_indexes[idx] for idx in indexes]
412
413 tup = tuplegetter(*indexes)
414
415 new_metadata = SimpleResultMetaData(
416 new_keys,
417 extra=extra,
418 _tuplefilter=tup,
419 _translated_indexes=indexes,
420 _processors=self._processors,
421 _create_unique_filters=self._create_unique_filters,
422 )
423
424 return new_metadata
425
426
427def result_tuple(
428 fields: Sequence[str], extra: Optional[Any] = None
429) -> Callable[[Iterable[Any]], Row[Unpack[TupleAny]]]:
430 parent = SimpleResultMetaData(fields, extra)
431 return functools.partial(
432 Row, parent, parent._effective_processors, parent._key_to_index
433 )
434
435
436class ResultInternal(InPlaceGenerative, BaseResultInternal[_R]):
437 __slots__ = ()
438 _is_cursor = False
439
440 @_generative
441 def _column_slices(self, indexes: Sequence[_KeyIndexType]) -> Self:
442 real_result = (
443 self._real_result
444 if self._real_result
445 else cast("Result[Any]", self)
446 )
447
448 if not real_result._source_supports_scalars or len(indexes) != 1:
449 self._metadata = self._metadata._reduce(indexes)
450
451 assert self._generate_rows
452
453 return self
454
455
456class _WithKeys:
457 __slots__ = ()
458
459 _metadata: ResultMetaData
460
461 # used mainly to share documentation on the keys method.
462 def keys(self) -> RMKeyView:
463 """Return an iterable view which yields the string keys that would
464 be represented by each :class:`_engine.Row`.
465
466 The keys can represent the labels of the columns returned by a core
467 statement or the names of the orm classes returned by an orm
468 execution.
469
470 The view also can be tested for key containment using the Python
471 ``in`` operator, which will test both for the string keys represented
472 in the view, as well as for alternate keys such as column objects.
473
474 .. versionchanged:: 1.4 a key view object is returned rather than a
475 plain list.
476
477
478 """
479 return self._metadata.keys
480
481
482class Result(_WithKeys, ResultInternal[Row[Unpack[_Ts]]]):
483 """Represent a set of database results.
484
485 .. versionadded:: 1.4 The :class:`_engine.Result` object provides a
486 completely updated usage model and calling facade for SQLAlchemy
487 Core and SQLAlchemy ORM. In Core, it forms the basis of the
488 :class:`_engine.CursorResult` object which replaces the previous
489 :class:`_engine.ResultProxy` interface. When using the ORM, a
490 higher level object called :class:`_engine.ChunkedIteratorResult`
491 is normally used.
492
493 .. note:: In SQLAlchemy 1.4 and above, this object is
494 used for ORM results returned by :meth:`_orm.Session.execute`, which can
495 yield instances of ORM mapped objects either individually or within
496 tuple-like rows. Note that the :class:`_engine.Result` object does not
497 deduplicate instances or rows automatically as is the case with the
498 legacy :class:`_orm.Query` object. For in-Python de-duplication of
499 instances or rows, use the :meth:`_engine.Result.unique` modifier
500 method.
501
502 .. seealso::
503
504 :ref:`tutorial_fetching_rows` - in the :doc:`/tutorial/index`
505
506 """
507
508 __slots__ = ("_metadata", "__dict__")
509
510 _row_logging_fn: Optional[
511 Callable[[Row[Unpack[TupleAny]]], Row[Unpack[TupleAny]]]
512 ] = None
513
514 _source_supports_scalars: bool = False
515
516 _yield_per: Optional[int] = None
517
518 _attributes: util.immutabledict[Any, Any] = util.immutabledict()
519
520 def __init__(self, cursor_metadata: ResultMetaData):
521 self._metadata = cursor_metadata
522
523 def __enter__(self) -> Self:
524 return self
525
526 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
527 self.close()
528
529 def close(self) -> None:
530 """Hard close this :class:`_engine.Result`.
531
532 The behavior of this method is implementation specific, and is
533 not implemented by default. The method should generally end
534 the resources in use by the result object and also cause any
535 subsequent iteration or row fetching to raise
536 :class:`.ResourceClosedError`.
537
538 .. versionadded:: 1.4.27 - ``.close()`` was previously not generally
539 available for all :class:`_engine.Result` classes, instead only
540 being available on the :class:`_engine.CursorResult` returned for
541 Core statement executions. As most other result objects, namely the
542 ones used by the ORM, are proxying a :class:`_engine.CursorResult`
543 in any case, this allows the underlying cursor result to be closed
544 from the outside facade for the case when the ORM query is using
545 the ``yield_per`` execution option where it does not immediately
546 exhaust and autoclose the database cursor.
547
548 """
549 self._soft_close(hard=True)
550
551 @property
552 def _soft_closed(self) -> bool:
553 raise NotImplementedError()
554
555 @property
556 def closed(self) -> bool:
557 """Return ``True`` if this :class:`_engine.Result` was **hard closed**
558 by explicitly calling the :meth:`close` method.
559
560 The attribute is **not** True if the :class:`_engine.Result` was only
561 **soft closed**; a "soft close" is the style of close that takes place
562 for example when the :class:`.CursorResult` is returned for a DML
563 only statement without RETURNING, or when all result rows are fetched.
564
565 .. seealso::
566
567 :attr:`.CursorResult.returns_rows` - attribute specific to
568 :class:`.CursorResult` which indicates if the result is one that
569 may return zero or more rows
570
571 """
572 raise NotImplementedError()
573
574 @_generative
575 def yield_per(self, num: int) -> Self:
576 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
577
578 This impacts the underlying behavior of the result when iterating over
579 the result object, or otherwise making use of methods such as
580 :meth:`_engine.Result.fetchone` that return one row at a time. Data
581 from the underlying cursor or other data source will be buffered up to
582 this many rows in memory, and the buffered collection will then be
583 yielded out one row at a time or as many rows are requested. Each time
584 the buffer clears, it will be refreshed to this many rows or as many
585 rows remain if fewer remain.
586
587 The :meth:`_engine.Result.yield_per` method is generally used in
588 conjunction with the
589 :paramref:`_engine.Connection.execution_options.stream_results`
590 execution option, which will allow the database dialect in use to make
591 use of a server side cursor, if the DBAPI supports a specific "server
592 side cursor" mode separate from its default mode of operation.
593
594 .. tip::
595
596 Consider using the
597 :paramref:`_engine.Connection.execution_options.yield_per`
598 execution option, which will simultaneously set
599 :paramref:`_engine.Connection.execution_options.stream_results`
600 to ensure the use of server side cursors, as well as automatically
601 invoke the :meth:`_engine.Result.yield_per` method to establish
602 a fixed row buffer size at once.
603
604 The :paramref:`_engine.Connection.execution_options.yield_per`
605 execution option is available for ORM operations, with
606 :class:`_orm.Session`-oriented use described at
607 :ref:`orm_queryguide_yield_per`. The Core-only version which works
608 with :class:`_engine.Connection` is new as of SQLAlchemy 1.4.40.
609
610 .. versionadded:: 1.4
611
612 :param num: number of rows to fetch each time the buffer is refilled.
613 If set to a value below 1, fetches all rows for the next buffer.
614
615 .. seealso::
616
617 :ref:`engine_stream_results` - describes Core behavior for
618 :meth:`_engine.Result.yield_per`
619
620 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
621
622 """
623 self._yield_per = num
624 return self
625
626 @_generative
627 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
628 """Apply unique filtering to the objects returned by this
629 :class:`_engine.Result`.
630
631 When this filter is applied with no arguments, the rows or objects
632 returned will filtered such that each row is returned uniquely. The
633 algorithm used to determine this uniqueness is by default the Python
634 hashing identity of the whole tuple. In some cases a specialized
635 per-entity hashing scheme may be used, such as when using the ORM, a
636 scheme is applied which works against the primary key identity of
637 returned objects.
638
639 The unique filter is applied **after all other filters**, which means
640 if the columns returned have been refined using a method such as the
641 :meth:`_engine.Result.columns` or :meth:`_engine.Result.scalars`
642 method, the uniquing is applied to **only the column or columns
643 returned**. This occurs regardless of the order in which these
644 methods have been called upon the :class:`_engine.Result` object.
645
646 The unique filter also changes the calculus used for methods like
647 :meth:`_engine.Result.fetchmany` and :meth:`_engine.Result.partitions`.
648 When using :meth:`_engine.Result.unique`, these methods will continue
649 to yield the number of rows or objects requested, after uniquing
650 has been applied. However, this necessarily impacts the buffering
651 behavior of the underlying cursor or datasource, such that multiple
652 underlying calls to ``cursor.fetchmany()`` may be necessary in order
653 to accumulate enough objects in order to provide a unique collection
654 of the requested size.
655
656 :param strategy: a callable that will be applied to rows or objects
657 being iterated, which should return an object that represents the
658 unique value of the row. A Python ``set()`` is used to store
659 these identities. If not passed, a default uniqueness strategy
660 is used which may have been assembled by the source of this
661 :class:`_engine.Result` object.
662
663 """
664 self._unique_filter_state = (set(), strategy)
665 return self
666
667 def columns(self, *col_expressions: _KeyIndexType) -> Self:
668 r"""Establish the columns that should be returned in each row.
669
670 This method may be used to limit the columns returned as well
671 as to reorder them. The given list of expressions are normally
672 a series of integers or string key names. They may also be
673 appropriate :class:`.ColumnElement` objects which correspond to
674 a given statement construct.
675
676 .. versionchanged:: 2.0 Due to a bug in 1.4, the
677 :meth:`_engine.Result.columns` method had an incorrect behavior
678 where calling upon the method with just one index would cause the
679 :class:`_engine.Result` object to yield scalar values rather than
680 :class:`_engine.Row` objects. In version 2.0, this behavior
681 has been corrected such that calling upon
682 :meth:`_engine.Result.columns` with a single index will
683 produce a :class:`_engine.Result` object that continues
684 to yield :class:`_engine.Row` objects, which include
685 only a single column.
686
687 E.g.::
688
689 statement = select(table.c.x, table.c.y, table.c.z)
690 result = connection.execute(statement)
691
692 for z, y in result.columns("z", "y"):
693 ...
694
695 Example of using the column objects from the statement itself::
696
697 for z, y in result.columns(
698 statement.selected_columns.c.z, statement.selected_columns.c.y
699 ):
700 ...
701
702 .. versionadded:: 1.4
703
704 :param \*col_expressions: indicates columns to be returned. Elements
705 may be integer row indexes, string column names, or appropriate
706 :class:`.ColumnElement` objects corresponding to a select construct.
707
708 :return: this :class:`_engine.Result` object with the modifications
709 given.
710
711 """
712 return self._column_slices(col_expressions)
713
714 @overload
715 def scalars(self: Result[_T, Unpack[TupleAny]]) -> ScalarResult[_T]: ...
716
717 @overload
718 def scalars(
719 self: Result[_T, Unpack[TupleAny]], index: Literal[0]
720 ) -> ScalarResult[_T]: ...
721
722 @overload
723 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]: ...
724
725 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]:
726 """Return a :class:`_engine.ScalarResult` filtering object which
727 will return single elements rather than :class:`_row.Row` objects.
728
729 E.g.::
730
731 >>> result = conn.execute(text("select int_id from table"))
732 >>> result.scalars().all()
733 [1, 2, 3]
734
735 When results are fetched from the :class:`_engine.ScalarResult`
736 filtering object, the single column-row that would be returned by the
737 :class:`_engine.Result` is instead returned as the column's value.
738
739 .. versionadded:: 1.4
740
741 :param index: integer or row key indicating the column to be fetched
742 from each row, defaults to ``0`` indicating the first column.
743
744 :return: a new :class:`_engine.ScalarResult` filtering object referring
745 to this :class:`_engine.Result` object.
746
747 """
748 return ScalarResult(self, index)
749
750 def _getter(
751 self, key: _KeyIndexType, raiseerr: bool = True
752 ) -> Optional[Callable[[Row[Unpack[TupleAny]]], Any]]:
753 """return a callable that will retrieve the given key from a
754 :class:`_engine.Row`.
755
756 """
757 if self._source_supports_scalars:
758 raise NotImplementedError(
759 "can't use this function in 'only scalars' mode"
760 )
761 return self._metadata._getter(key, raiseerr)
762
763 def _tuple_getter(self, keys: Sequence[_KeyIndexType]) -> _TupleGetterType:
764 """return a callable that will retrieve the given keys from a
765 :class:`_engine.Row`.
766
767 """
768 if self._source_supports_scalars:
769 raise NotImplementedError(
770 "can't use this function in 'only scalars' mode"
771 )
772 return self._metadata._row_as_tuple_getter(keys)
773
774 def mappings(self) -> MappingResult:
775 """Apply a mappings filter to returned rows, returning an instance of
776 :class:`_engine.MappingResult`.
777
778 When this filter is applied, fetching rows will return
779 :class:`_engine.RowMapping` objects instead of :class:`_engine.Row`
780 objects.
781
782 .. versionadded:: 1.4
783
784 :return: a new :class:`_engine.MappingResult` filtering object
785 referring to this :class:`_engine.Result` object.
786
787 """
788
789 return MappingResult(self)
790
791 @property
792 @deprecated(
793 "2.1.0",
794 "The :attr:`.Result.t` method is deprecated, :class:`.Row` "
795 "now behaves like a tuple and can unpack types directly.",
796 )
797 def t(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
798 """Apply a "typed tuple" typing filter to returned rows.
799
800 The :attr:`_engine.Result.t` attribute is a synonym for
801 calling the :meth:`_engine.Result.tuples` method.
802
803 .. versionadded:: 2.0
804
805 .. seealso::
806
807 :ref:`change_10635` - describes a migration path from this
808 workaround for SQLAlchemy 2.1.
809
810 """
811 return self # type: ignore
812
813 @deprecated(
814 "2.1.0",
815 "The :meth:`.Result.tuples` method is deprecated, :class:`.Row` "
816 "now behaves like a tuple and can unpack types directly.",
817 )
818 def tuples(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
819 """Apply a "typed tuple" typing filter to returned rows.
820
821 This method returns the same :class:`_engine.Result` object
822 at runtime,
823 however annotates as returning a :class:`_engine.TupleResult` object
824 that will indicate to :pep:`484` typing tools that plain typed
825 ``Tuple`` instances are returned rather than rows. This allows
826 tuple unpacking and ``__getitem__`` access of :class:`_engine.Row`
827 objects to by typed, for those cases where the statement invoked
828 itself included typing information.
829
830 .. versionadded:: 2.0
831
832 :return: the :class:`_engine.TupleResult` type at typing time.
833
834 .. seealso::
835
836 :ref:`change_10635` - describes a migration path from this
837 workaround for SQLAlchemy 2.1.
838
839 :attr:`_engine.Result.t` - shorter synonym
840
841 :attr:`_engine.Row._t` - :class:`_engine.Row` version
842
843 """
844
845 return self # type: ignore
846
847 def _raw_row_iterator(self) -> Iterator[_RowData]:
848 """Return a safe iterator that yields raw row data.
849
850 This is used by the :meth:`_engine.Result.merge` method
851 to merge multiple compatible results together.
852
853 """
854 raise NotImplementedError()
855
856 def __iter__(self) -> Iterator[Row[Unpack[_Ts]]]:
857 return self._iter_impl()
858
859 def __next__(self) -> Row[Unpack[_Ts]]:
860 return self._next_impl()
861
862 def partitions(
863 self, size: Optional[int] = None
864 ) -> Iterator[Sequence[Row[Unpack[_Ts]]]]:
865 """Iterate through sub-lists of rows of the size given.
866
867 Each list will be of the size given, excluding the last list to
868 be yielded, which may have a small number of rows. No empty
869 lists will be yielded.
870
871 The result object is automatically closed when the iterator
872 is fully consumed.
873
874 Note that the backend driver will usually buffer the entire result
875 ahead of time unless the
876 :paramref:`.Connection.execution_options.stream_results` execution
877 option is used indicating that the driver should not pre-buffer
878 results, if possible. Not all drivers support this option and
879 the option is silently ignored for those who do not.
880
881 When using the ORM, the :meth:`_engine.Result.partitions` method
882 is typically more effective from a memory perspective when it is
883 combined with use of the
884 :ref:`yield_per execution option <orm_queryguide_yield_per>`,
885 which instructs both the DBAPI driver to use server side cursors,
886 if available, as well as instructs the ORM loading internals to only
887 build a certain amount of ORM objects from a result at a time before
888 yielding them out.
889
890 .. versionadded:: 1.4
891
892 :param size: indicate the maximum number of rows to be present
893 in each list yielded. If None, makes use of the value set by
894 the :meth:`_engine.Result.yield_per`, method, if it were called,
895 or the :paramref:`_engine.Connection.execution_options.yield_per`
896 execution option, which is equivalent in this regard. If
897 yield_per weren't set, it makes use of the
898 :meth:`_engine.Result.fetchmany` default, which may be backend
899 specific and not well defined.
900
901 :return: iterator of lists
902
903 .. seealso::
904
905 :ref:`engine_stream_results`
906
907 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
908
909 """
910
911 getter = self._manyrow_getter
912
913 while True:
914 partition = getter(self, size)
915 if partition:
916 yield partition
917 else:
918 break
919
920 def fetchall(self) -> Sequence[Row[Unpack[_Ts]]]:
921 """A synonym for the :meth:`_engine.Result.all` method."""
922
923 return self._allrows()
924
925 def fetchone(self) -> Optional[Row[Unpack[_Ts]]]:
926 """Fetch one row.
927
928 When all rows are exhausted, returns None.
929
930 This method is provided for backwards compatibility with
931 SQLAlchemy 1.x.x.
932
933 To fetch the first row of a result only, use the
934 :meth:`_engine.Result.first` method. To iterate through all
935 rows, iterate the :class:`_engine.Result` object directly.
936
937 :return: a :class:`_engine.Row` object if no filters are applied,
938 or ``None`` if no rows remain.
939
940 """
941 row = self._onerow_getter(self)
942 if row is _NO_ROW:
943 return None
944 else:
945 return row
946
947 def fetchmany(
948 self, size: Optional[int] = None
949 ) -> Sequence[Row[Unpack[_Ts]]]:
950 """Fetch many rows.
951
952 When all rows are exhausted, returns an empty sequence.
953
954 This method is provided for backwards compatibility with
955 SQLAlchemy 1.x.x.
956
957 To fetch rows in groups, use the :meth:`_engine.Result.partitions`
958 method.
959
960 :return: a sequence of :class:`_engine.Row` objects.
961
962 .. seealso::
963
964 :meth:`_engine.Result.partitions`
965
966 """
967
968 return self._manyrow_getter(self, size)
969
970 def all(self) -> Sequence[Row[Unpack[_Ts]]]:
971 """Return all rows in a sequence.
972
973 Closes the result set after invocation. Subsequent invocations
974 will return an empty sequence.
975
976 .. versionadded:: 1.4
977
978 :return: a sequence of :class:`_engine.Row` objects.
979
980 .. seealso::
981
982 :ref:`engine_stream_results` - How to stream a large result set
983 without loading it completely in python.
984
985 """
986
987 return self._allrows()
988
989 def first(self) -> Optional[Row[Unpack[_Ts]]]:
990 """Fetch the first row or ``None`` if no row is present.
991
992 Closes the result set and discards remaining rows.
993
994 .. note:: This method returns one **row**, e.g. tuple, by default.
995 To return exactly one single scalar value, that is, the first
996 column of the first row, use the
997 :meth:`_engine.Result.scalar` method,
998 or combine :meth:`_engine.Result.scalars` and
999 :meth:`_engine.Result.first`.
1000
1001 Additionally, in contrast to the behavior of the legacy ORM
1002 :meth:`_orm.Query.first` method, **no limit is applied** to the
1003 SQL query which was invoked to produce this
1004 :class:`_engine.Result`;
1005 for a DBAPI driver that buffers results in memory before yielding
1006 rows, all rows will be sent to the Python process and all but
1007 the first row will be discarded.
1008
1009 .. seealso::
1010
1011 :ref:`migration_20_unify_select`
1012
1013 :return: a :class:`_engine.Row` object, or None
1014 if no rows remain.
1015
1016 .. seealso::
1017
1018 :meth:`_engine.Result.scalar`
1019
1020 :meth:`_engine.Result.one`
1021
1022 """
1023
1024 return self._only_one_row(
1025 raise_for_second_row=False, raise_for_none=False, scalar=False
1026 )
1027
1028 def one_or_none(self) -> Optional[Row[Unpack[_Ts]]]:
1029 """Return at most one result or raise an exception.
1030
1031 Returns ``None`` if the result has no rows.
1032 Raises :class:`.MultipleResultsFound`
1033 if multiple rows are returned.
1034
1035 .. versionadded:: 1.4
1036
1037 :return: The first :class:`_engine.Row` or ``None`` if no row
1038 is available.
1039
1040 :raises: :class:`.MultipleResultsFound`
1041
1042 .. seealso::
1043
1044 :meth:`_engine.Result.first`
1045
1046 :meth:`_engine.Result.one`
1047
1048 """
1049 return self._only_one_row(
1050 raise_for_second_row=True, raise_for_none=False, scalar=False
1051 )
1052
1053 def scalar_one(self: Result[_T, Unpack[TupleAny]]) -> _T:
1054 """Return exactly one scalar result or raise an exception.
1055
1056 This is equivalent to calling :meth:`_engine.Result.scalars` and
1057 then :meth:`_engine.ScalarResult.one`.
1058
1059 .. seealso::
1060
1061 :meth:`_engine.ScalarResult.one`
1062
1063 :meth:`_engine.Result.scalars`
1064
1065 """
1066 return self._only_one_row(
1067 raise_for_second_row=True, raise_for_none=True, scalar=True
1068 )
1069
1070 def scalar_one_or_none(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1071 """Return exactly one scalar result or ``None``.
1072
1073 This is equivalent to calling :meth:`_engine.Result.scalars` and
1074 then :meth:`_engine.ScalarResult.one_or_none`.
1075
1076 .. seealso::
1077
1078 :meth:`_engine.ScalarResult.one_or_none`
1079
1080 :meth:`_engine.Result.scalars`
1081
1082 """
1083 return self._only_one_row(
1084 raise_for_second_row=True, raise_for_none=False, scalar=True
1085 )
1086
1087 def one(self) -> Row[Unpack[_Ts]]:
1088 """Return exactly one row or raise an exception.
1089
1090 Raises :class:`_exc.NoResultFound` if the result returns no
1091 rows, or :class:`_exc.MultipleResultsFound` if multiple rows
1092 would be returned.
1093
1094 .. note:: This method returns one **row**, e.g. tuple, by default.
1095 To return exactly one single scalar value, that is, the first
1096 column of the first row, use the
1097 :meth:`_engine.Result.scalar_one` method, or combine
1098 :meth:`_engine.Result.scalars` and
1099 :meth:`_engine.Result.one`.
1100
1101 .. versionadded:: 1.4
1102
1103 :return: The first :class:`_engine.Row`.
1104
1105 :raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`
1106
1107 .. seealso::
1108
1109 :meth:`_engine.Result.first`
1110
1111 :meth:`_engine.Result.one_or_none`
1112
1113 :meth:`_engine.Result.scalar_one`
1114
1115 """
1116 return self._only_one_row(
1117 raise_for_second_row=True, raise_for_none=True, scalar=False
1118 )
1119
1120 # special case to handle mypy issue:
1121 # https://github.com/python/mypy/issues/20651
1122 @overload
1123 def scalar(self: Result[Never, Unpack[TupleAny]]) -> Optional[Any]:
1124 pass
1125
1126 @overload
1127 def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1128 pass
1129
1130 def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1131 """Fetch the first column of the first row, and close the result set.
1132
1133 Returns ``None`` if there are no rows to fetch.
1134
1135 No validation is performed to test if additional rows remain.
1136
1137 After calling this method, the object is fully closed,
1138 e.g. the :meth:`_engine.CursorResult.close`
1139 method will have been called.
1140
1141 :return: a Python scalar value, or ``None`` if no rows remain.
1142
1143 """
1144 return self._only_one_row(
1145 raise_for_second_row=False, raise_for_none=False, scalar=True
1146 )
1147
1148 def freeze(self) -> FrozenResult[Unpack[_Ts]]:
1149 """Return a callable object that will produce copies of this
1150 :class:`_engine.Result` when invoked.
1151
1152 The callable object returned is an instance of
1153 :class:`_engine.FrozenResult`.
1154
1155 This is used for result set caching. The method must be called
1156 on the result when it has been unconsumed, and calling the method
1157 will consume the result fully. When the :class:`_engine.FrozenResult`
1158 is retrieved from a cache, it can be called any number of times where
1159 it will produce a new :class:`_engine.Result` object each time
1160 against its stored set of rows.
1161
1162 .. seealso::
1163
1164 :ref:`do_orm_execute_re_executing` - example usage within the
1165 ORM to implement a result-set cache.
1166
1167 """
1168
1169 return FrozenResult(self)
1170
1171 def merge(
1172 self, *others: Result[Unpack[TupleAny]]
1173 ) -> MergedResult[Unpack[TupleAny]]:
1174 """Merge this :class:`_engine.Result` with other compatible result
1175 objects.
1176
1177 The object returned is an instance of :class:`_engine.MergedResult`,
1178 which will be composed of iterators from the given result
1179 objects.
1180
1181 The new result will use the metadata from this result object.
1182 The subsequent result objects must be against an identical
1183 set of result / cursor metadata, otherwise the behavior is
1184 undefined.
1185
1186 """
1187 return MergedResult(self._metadata, (self,) + others)
1188
1189
1190class FilterResult(ResultInternal[_R]):
1191 """A wrapper for a :class:`_engine.Result` that returns objects other than
1192 :class:`_engine.Row` objects, such as dictionaries or scalar objects.
1193
1194 :class:`_engine.FilterResult` is the common base for additional result
1195 APIs including :class:`_engine.MappingResult`,
1196 :class:`_engine.ScalarResult` and :class:`_engine.AsyncResult`.
1197
1198 """
1199
1200 __slots__ = (
1201 "_real_result",
1202 "_post_creational_filter",
1203 "_metadata",
1204 "_unique_filter_state",
1205 "__dict__",
1206 )
1207
1208 _post_creational_filter: Optional[Callable[[Any], Any]]
1209
1210 _real_result: Result[Unpack[TupleAny]]
1211
1212 def __enter__(self) -> Self:
1213 return self
1214
1215 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
1216 self._real_result.__exit__(type_, value, traceback)
1217
1218 @_generative
1219 def yield_per(self, num: int) -> Self:
1220 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
1221
1222 The :meth:`_engine.FilterResult.yield_per` method is a pass through
1223 to the :meth:`_engine.Result.yield_per` method. See that method's
1224 documentation for usage notes.
1225
1226 .. versionadded:: 1.4.40 - added :meth:`_engine.FilterResult.yield_per`
1227 so that the method is available on all result set implementations
1228
1229 .. seealso::
1230
1231 :ref:`engine_stream_results` - describes Core behavior for
1232 :meth:`_engine.Result.yield_per`
1233
1234 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1235
1236 """
1237 self._real_result = self._real_result.yield_per(num)
1238 return self
1239
1240 def _soft_close(self, hard: bool = False) -> None:
1241 self._real_result._soft_close(hard=hard)
1242
1243 @property
1244 def _soft_closed(self) -> bool:
1245 return self._real_result._soft_closed
1246
1247 @property
1248 def closed(self) -> bool:
1249 """Return ``True`` if this :class:`_engine.Result` being
1250 proxied by this :class:`_engine.FilterResult` was
1251 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
1252 method.
1253
1254 This is a direct proxy for the :attr:`_engine.Result.closed` attribute;
1255 see that attribute for details.
1256
1257 """
1258 return self._real_result.closed
1259
1260 def close(self) -> None:
1261 """Close this :class:`_engine.FilterResult`.
1262
1263 .. versionadded:: 1.4.43
1264
1265 """
1266 self._real_result.close()
1267
1268 @property
1269 def _attributes(self) -> Dict[Any, Any]:
1270 return self._real_result._attributes
1271
1272 def _fetchiter_impl(
1273 self,
1274 ) -> Iterator[_InterimRowType[Row[Unpack[TupleAny]]]]:
1275 return self._real_result._fetchiter_impl()
1276
1277 def _fetchone_impl(
1278 self, hard_close: bool = False
1279 ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
1280 return self._real_result._fetchone_impl(hard_close=hard_close)
1281
1282 def _fetchall_impl(
1283 self,
1284 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1285 return self._real_result._fetchall_impl()
1286
1287 def _fetchmany_impl(
1288 self, size: Optional[int] = None
1289 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1290 return self._real_result._fetchmany_impl(size=size)
1291
1292
1293class ScalarResult(FilterResult[_R]):
1294 """A wrapper for a :class:`_engine.Result` that returns scalar values
1295 rather than :class:`_row.Row` values.
1296
1297 The :class:`_engine.ScalarResult` object is acquired by calling the
1298 :meth:`_engine.Result.scalars` method.
1299
1300 A special limitation of :class:`_engine.ScalarResult` is that it has
1301 no ``fetchone()`` method; since the semantics of ``fetchone()`` are that
1302 the ``None`` value indicates no more results, this is not compatible
1303 with :class:`_engine.ScalarResult` since there is no way to distinguish
1304 between ``None`` as a row value versus ``None`` as an indicator. Use
1305 ``next(result)`` to receive values individually.
1306
1307 """
1308
1309 __slots__ = ()
1310
1311 _generate_rows = False
1312
1313 _post_creational_filter: Optional[Callable[[Any], Any]]
1314
1315 def __init__(
1316 self, real_result: Result[Unpack[TupleAny]], index: _KeyIndexType
1317 ):
1318 self._real_result = real_result
1319
1320 if real_result._source_supports_scalars:
1321 self._metadata = real_result._metadata
1322 self._post_creational_filter = None
1323 else:
1324 self._metadata = real_result._metadata._reduce([index])
1325 self._post_creational_filter = operator.itemgetter(0)
1326
1327 self._unique_filter_state = real_result._unique_filter_state
1328
1329 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1330 """Apply unique filtering to the objects returned by this
1331 :class:`_engine.ScalarResult`.
1332
1333 See :meth:`_engine.Result.unique` for usage details.
1334
1335 """
1336 self._unique_filter_state = (set(), strategy)
1337 return self
1338
1339 def partitions(self, size: Optional[int] = None) -> Iterator[Sequence[_R]]:
1340 """Iterate through sub-lists of elements of the size given.
1341
1342 Equivalent to :meth:`_engine.Result.partitions` except that
1343 scalar values, rather than :class:`_engine.Row` objects,
1344 are returned.
1345
1346 """
1347
1348 getter = self._manyrow_getter
1349
1350 while True:
1351 partition = getter(self, size)
1352 if partition:
1353 yield partition
1354 else:
1355 break
1356
1357 def fetchall(self) -> Sequence[_R]:
1358 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1359
1360 return self._allrows()
1361
1362 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1363 """Fetch many objects.
1364
1365 Equivalent to :meth:`_engine.Result.fetchmany` except that
1366 scalar values, rather than :class:`_engine.Row` objects,
1367 are returned.
1368
1369 """
1370 return self._manyrow_getter(self, size)
1371
1372 def all(self) -> Sequence[_R]:
1373 """Return all scalar values in a sequence.
1374
1375 Equivalent to :meth:`_engine.Result.all` except that
1376 scalar values, rather than :class:`_engine.Row` objects,
1377 are returned.
1378
1379 """
1380 return self._allrows()
1381
1382 def __iter__(self) -> Iterator[_R]:
1383 return self._iter_impl()
1384
1385 def __next__(self) -> _R:
1386 return self._next_impl()
1387
1388 def first(self) -> Optional[_R]:
1389 """Fetch the first object or ``None`` if no object is present.
1390
1391 Equivalent to :meth:`_engine.Result.first` except that
1392 scalar values, rather than :class:`_engine.Row` objects,
1393 are returned.
1394
1395
1396 """
1397 return self._only_one_row(
1398 raise_for_second_row=False, raise_for_none=False, scalar=False
1399 )
1400
1401 def one_or_none(self) -> Optional[_R]:
1402 """Return at most one object or raise an exception.
1403
1404 Equivalent to :meth:`_engine.Result.one_or_none` except that
1405 scalar values, rather than :class:`_engine.Row` objects,
1406 are returned.
1407
1408 """
1409 return self._only_one_row(
1410 raise_for_second_row=True, raise_for_none=False, scalar=False
1411 )
1412
1413 def one(self) -> _R:
1414 """Return exactly one object or raise an exception.
1415
1416 Equivalent to :meth:`_engine.Result.one` except that
1417 scalar values, rather than :class:`_engine.Row` objects,
1418 are returned.
1419
1420 """
1421 return self._only_one_row(
1422 raise_for_second_row=True, raise_for_none=True, scalar=False
1423 )
1424
1425
1426class TupleResult(FilterResult[_R], util.TypingOnly):
1427 """A :class:`_engine.Result` that's typed as returning plain
1428 Python tuples instead of rows.
1429
1430 Since :class:`_engine.Row` acts like a tuple in every way already,
1431 this class is a typing only class, regular :class:`_engine.Result` is
1432 still used at runtime.
1433
1434 """
1435
1436 __slots__ = ()
1437
1438 if TYPE_CHECKING:
1439
1440 def partitions(
1441 self, size: Optional[int] = None
1442 ) -> Iterator[Sequence[_R]]:
1443 """Iterate through sub-lists of elements of the size given.
1444
1445 Equivalent to :meth:`_engine.Result.partitions` except that
1446 tuple values, rather than :class:`_engine.Row` objects,
1447 are returned.
1448
1449 """
1450 ...
1451
1452 def fetchone(self) -> Optional[_R]:
1453 """Fetch one tuple.
1454
1455 Equivalent to :meth:`_engine.Result.fetchone` except that
1456 tuple values, rather than :class:`_engine.Row`
1457 objects, are returned.
1458
1459 """
1460 ...
1461
1462 def fetchall(self) -> Sequence[_R]:
1463 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1464 ...
1465
1466 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1467 """Fetch many objects.
1468
1469 Equivalent to :meth:`_engine.Result.fetchmany` except that
1470 tuple values, rather than :class:`_engine.Row` objects,
1471 are returned.
1472
1473 """
1474 ...
1475
1476 def all(self) -> Sequence[_R]: # noqa: A001
1477 """Return all scalar values in a sequence.
1478
1479 Equivalent to :meth:`_engine.Result.all` except that
1480 tuple values, rather than :class:`_engine.Row` objects,
1481 are returned.
1482
1483 """
1484 ...
1485
1486 def __iter__(self) -> Iterator[_R]: ...
1487
1488 def __next__(self) -> _R: ...
1489
1490 def first(self) -> Optional[_R]:
1491 """Fetch the first object or ``None`` if no object is present.
1492
1493 Equivalent to :meth:`_engine.Result.first` except that
1494 tuple values, rather than :class:`_engine.Row` objects,
1495 are returned.
1496
1497
1498 """
1499 ...
1500
1501 def one_or_none(self) -> Optional[_R]:
1502 """Return at most one object or raise an exception.
1503
1504 Equivalent to :meth:`_engine.Result.one_or_none` except that
1505 tuple values, rather than :class:`_engine.Row` objects,
1506 are returned.
1507
1508 """
1509 ...
1510
1511 def one(self) -> _R:
1512 """Return exactly one object or raise an exception.
1513
1514 Equivalent to :meth:`_engine.Result.one` except that
1515 tuple values, rather than :class:`_engine.Row` objects,
1516 are returned.
1517
1518 """
1519 ...
1520
1521 @overload
1522 def scalar_one(self: TupleResult[Tuple[_T]]) -> _T: ...
1523
1524 @overload
1525 def scalar_one(self) -> Any: ...
1526
1527 def scalar_one(self) -> Any:
1528 """Return exactly one scalar result or raise an exception.
1529
1530 This is equivalent to calling :meth:`_engine.Result.scalars`
1531 and then :meth:`_engine.ScalarResult.one`.
1532
1533 .. seealso::
1534
1535 :meth:`_engine.ScalarResult.one`
1536
1537 :meth:`_engine.Result.scalars`
1538
1539 """
1540 ...
1541
1542 @overload
1543 def scalar_one_or_none(
1544 self: TupleResult[Tuple[_T]],
1545 ) -> Optional[_T]: ...
1546
1547 @overload
1548 def scalar_one_or_none(self) -> Optional[Any]: ...
1549
1550 def scalar_one_or_none(self) -> Optional[Any]:
1551 """Return exactly one or no scalar result.
1552
1553 This is equivalent to calling :meth:`_engine.Result.scalars`
1554 and then :meth:`_engine.ScalarResult.one_or_none`.
1555
1556 .. seealso::
1557
1558 :meth:`_engine.ScalarResult.one_or_none`
1559
1560 :meth:`_engine.Result.scalars`
1561
1562 """
1563 ...
1564
1565 @overload
1566 def scalar(self: TupleResult[Tuple[_T]]) -> Optional[_T]: ...
1567
1568 @overload
1569 def scalar(self) -> Any: ...
1570
1571 def scalar(self) -> Any:
1572 """Fetch the first column of the first row, and close the result
1573 set.
1574
1575 Returns ``None`` if there are no rows to fetch.
1576
1577 No validation is performed to test if additional rows remain.
1578
1579 After calling this method, the object is fully closed,
1580 e.g. the :meth:`_engine.CursorResult.close`
1581 method will have been called.
1582
1583 :return: a Python scalar value , or ``None`` if no rows remain.
1584
1585 """
1586 ...
1587
1588
1589class MappingResult(_WithKeys, FilterResult[RowMapping]):
1590 """A wrapper for a :class:`_engine.Result` that returns dictionary values
1591 rather than :class:`_engine.Row` values.
1592
1593 The :class:`_engine.MappingResult` object is acquired by calling the
1594 :meth:`_engine.Result.mappings` method.
1595
1596 """
1597
1598 __slots__ = ()
1599
1600 _generate_rows = True
1601
1602 _post_creational_filter = operator.attrgetter("_mapping")
1603
1604 def __init__(self, result: Result[Unpack[TupleAny]]):
1605 self._real_result = result
1606 self._unique_filter_state = result._unique_filter_state
1607 self._metadata = result._metadata
1608 if result._source_supports_scalars:
1609 self._metadata = self._metadata._reduce([0])
1610
1611 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1612 """Apply unique filtering to the objects returned by this
1613 :class:`_engine.MappingResult`.
1614
1615 See :meth:`_engine.Result.unique` for usage details.
1616
1617 """
1618 self._unique_filter_state = (set(), strategy)
1619 return self
1620
1621 def columns(self, *col_expressions: _KeyIndexType) -> Self:
1622 """Establish the columns that should be returned in each row."""
1623 return self._column_slices(col_expressions)
1624
1625 def partitions(
1626 self, size: Optional[int] = None
1627 ) -> Iterator[Sequence[RowMapping]]:
1628 """Iterate through sub-lists of elements of the size given.
1629
1630 Equivalent to :meth:`_engine.Result.partitions` except that
1631 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1632 objects, are returned.
1633
1634 """
1635
1636 getter = self._manyrow_getter
1637
1638 while True:
1639 partition = getter(self, size)
1640 if partition:
1641 yield partition
1642 else:
1643 break
1644
1645 def fetchall(self) -> Sequence[RowMapping]:
1646 """A synonym for the :meth:`_engine.MappingResult.all` method."""
1647
1648 return self._allrows()
1649
1650 def fetchone(self) -> Optional[RowMapping]:
1651 """Fetch one object.
1652
1653 Equivalent to :meth:`_engine.Result.fetchone` except that
1654 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1655 objects, are returned.
1656
1657 """
1658
1659 row = self._onerow_getter(self)
1660 if row is _NO_ROW:
1661 return None
1662 else:
1663 return row
1664
1665 def fetchmany(self, size: Optional[int] = None) -> Sequence[RowMapping]:
1666 """Fetch many objects.
1667
1668 Equivalent to :meth:`_engine.Result.fetchmany` except that
1669 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1670 objects, are returned.
1671
1672 """
1673
1674 return self._manyrow_getter(self, size)
1675
1676 def all(self) -> Sequence[RowMapping]:
1677 """Return all scalar values in a sequence.
1678
1679 Equivalent to :meth:`_engine.Result.all` except that
1680 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1681 objects, are returned.
1682
1683 """
1684
1685 return self._allrows()
1686
1687 def __iter__(self) -> Iterator[RowMapping]:
1688 return self._iter_impl()
1689
1690 def __next__(self) -> RowMapping:
1691 return self._next_impl()
1692
1693 def first(self) -> Optional[RowMapping]:
1694 """Fetch the first object or ``None`` if no object is present.
1695
1696 Equivalent to :meth:`_engine.Result.first` except that
1697 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1698 objects, are returned.
1699
1700
1701 """
1702 return self._only_one_row(
1703 raise_for_second_row=False, raise_for_none=False, scalar=False
1704 )
1705
1706 def one_or_none(self) -> Optional[RowMapping]:
1707 """Return at most one object or raise an exception.
1708
1709 Equivalent to :meth:`_engine.Result.one_or_none` except that
1710 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1711 objects, are returned.
1712
1713 """
1714 return self._only_one_row(
1715 raise_for_second_row=True, raise_for_none=False, scalar=False
1716 )
1717
1718 def one(self) -> RowMapping:
1719 """Return exactly one object or raise an exception.
1720
1721 Equivalent to :meth:`_engine.Result.one` except that
1722 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1723 objects, are returned.
1724
1725 """
1726 return self._only_one_row(
1727 raise_for_second_row=True, raise_for_none=True, scalar=False
1728 )
1729
1730
1731class FrozenResult(Generic[Unpack[_Ts]]):
1732 """Represents a :class:`_engine.Result` object in a "frozen" state suitable
1733 for caching.
1734
1735 The :class:`_engine.FrozenResult` object is returned from the
1736 :meth:`_engine.Result.freeze` method of any :class:`_engine.Result`
1737 object.
1738
1739 A new iterable :class:`_engine.Result` object is generated from a fixed
1740 set of data each time the :class:`_engine.FrozenResult` is invoked as
1741 a callable::
1742
1743
1744 result = connection.execute(query)
1745
1746 frozen = result.freeze()
1747
1748 unfrozen_result_one = frozen()
1749
1750 for row in unfrozen_result_one:
1751 print(row)
1752
1753 unfrozen_result_two = frozen()
1754 rows = unfrozen_result_two.all()
1755
1756 # ... etc
1757
1758 .. versionadded:: 1.4
1759
1760 .. seealso::
1761
1762 :ref:`do_orm_execute_re_executing` - example usage within the
1763 ORM to implement a result-set cache.
1764
1765 :func:`_orm.loading.merge_frozen_result` - ORM function to merge
1766 a frozen result back into a :class:`_orm.Session`.
1767
1768 """
1769
1770 data: Sequence[Any]
1771
1772 def __init__(self, result: Result[Unpack[_Ts]]):
1773 self.metadata = result._metadata._for_freeze()
1774 self._source_supports_scalars = result._source_supports_scalars
1775 self._attributes = result._attributes
1776
1777 if self._source_supports_scalars:
1778 self.data = list(result._raw_row_iterator())
1779 else:
1780 self.data = result.fetchall()
1781
1782 def _rewrite_rows(self) -> Sequence[Sequence[Any]]:
1783 # used only by the orm fn merge_frozen_result
1784 if self._source_supports_scalars:
1785 return [[elem] for elem in self.data]
1786 else:
1787 return [list(row) for row in self.data]
1788
1789 def with_new_rows(
1790 self, tuple_data: Sequence[Row[Unpack[_Ts]]]
1791 ) -> FrozenResult[Unpack[_Ts]]:
1792 fr = FrozenResult.__new__(FrozenResult)
1793 fr.metadata = self.metadata
1794 fr._attributes = self._attributes
1795 fr._source_supports_scalars = self._source_supports_scalars
1796
1797 if self._source_supports_scalars:
1798 fr.data = [d[0] for d in tuple_data] # type: ignore[misc]
1799 else:
1800 fr.data = tuple_data
1801 return fr
1802
1803 def __call__(self) -> Result[Unpack[_Ts]]:
1804 result: IteratorResult[Unpack[_Ts]] = IteratorResult(
1805 self.metadata, iter(self.data)
1806 )
1807 result._attributes = self._attributes
1808 result._source_supports_scalars = self._source_supports_scalars
1809 return result
1810
1811
1812class IteratorResult(Result[Unpack[_Ts]]):
1813 """A :class:`_engine.Result` that gets data from a Python iterator of
1814 :class:`_engine.Row` objects or similar row-like data.
1815
1816 .. versionadded:: 1.4
1817
1818 """
1819
1820 _hard_closed = False
1821 _soft_closed = False
1822
1823 def __init__(
1824 self,
1825 cursor_metadata: ResultMetaData,
1826 iterator: Iterator[_InterimSupportsScalarsRowType],
1827 raw: Optional[Result[Any]] = None,
1828 _source_supports_scalars: bool = False,
1829 context: Optional[Any] = None,
1830 ):
1831 self._metadata = cursor_metadata
1832 self.iterator = iterator
1833 self.raw = raw
1834 self._source_supports_scalars = _source_supports_scalars
1835 self.context = context
1836
1837 @property
1838 def closed(self) -> bool:
1839 """Return ``True`` if this :class:`_engine.IteratorResult` was
1840 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
1841 method.
1842
1843 The attribute is **not** True if the :class:`_engine.Result` was only
1844 **soft closed**; a "soft close" is the style of close that takes place
1845 for example when the :class:`.CursorResult` is returned for a DML
1846 only statement without RETURNING, or when all result rows are fetched.
1847
1848 .. seealso::
1849
1850 :attr:`.CursorResult.returns_rows` - attribute specific to
1851 :class:`.CursorResult` which indicates if the result is one that
1852 may return zero or more rows
1853
1854 """
1855 return self._hard_closed
1856
1857 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
1858 if hard:
1859 self._hard_closed = True
1860 if self.raw is not None:
1861 self.raw._soft_close(hard=hard, **kw)
1862 self.iterator = iter([])
1863 self._reset_memoizations()
1864 self._soft_closed = True
1865
1866 def _raise_hard_closed(self) -> NoReturn:
1867 raise exc.ResourceClosedError("This result object is closed.")
1868
1869 def _raw_row_iterator(self) -> Iterator[_RowData]:
1870 return self.iterator
1871
1872 def _fetchiter_impl(self) -> Iterator[_InterimSupportsScalarsRowType]:
1873 if self._hard_closed:
1874 self._raise_hard_closed()
1875 return self.iterator
1876
1877 def _fetchone_impl(
1878 self, hard_close: bool = False
1879 ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
1880 if self._hard_closed:
1881 self._raise_hard_closed()
1882
1883 row = next(self.iterator, _NO_ROW)
1884 if row is _NO_ROW:
1885 self._soft_close(hard=hard_close)
1886 return None
1887 else:
1888 return row
1889
1890 def _fetchall_impl(
1891 self,
1892 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1893 if self._hard_closed:
1894 self._raise_hard_closed()
1895 try:
1896 return list(self.iterator)
1897 finally:
1898 self._soft_close()
1899
1900 def _fetchmany_impl(
1901 self, size: Optional[int] = None
1902 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1903 if self._hard_closed:
1904 self._raise_hard_closed()
1905
1906 return list(itertools.islice(self.iterator, 0, size))
1907
1908
1909def null_result() -> IteratorResult[Any]:
1910 return IteratorResult(SimpleResultMetaData([]), iter([]))
1911
1912
1913class ChunkedIteratorResult(IteratorResult[Unpack[_Ts]]):
1914 """An :class:`_engine.IteratorResult` that works from an
1915 iterator-producing callable.
1916
1917 The given ``chunks`` argument is a function that is given a number of rows
1918 to return in each chunk, or ``None`` for all rows. The function should
1919 then return an un-consumed iterator of lists, each list of the requested
1920 size.
1921
1922 The function can be called at any time again, in which case it should
1923 continue from the same result set but adjust the chunk size as given.
1924
1925 .. versionadded:: 1.4
1926
1927 """
1928
1929 def __init__(
1930 self,
1931 cursor_metadata: ResultMetaData,
1932 chunks: Callable[
1933 [Optional[int]], Iterator[Sequence[_InterimRowType[_R]]]
1934 ],
1935 source_supports_scalars: bool = False,
1936 raw: Optional[Result[Any]] = None,
1937 dynamic_yield_per: bool = False,
1938 context: Optional[Any] = None,
1939 ):
1940 self._metadata = cursor_metadata
1941 self.chunks = chunks
1942 self._source_supports_scalars = source_supports_scalars
1943 self.raw = raw
1944 self.iterator = itertools.chain.from_iterable(self.chunks(None))
1945 self.dynamic_yield_per = dynamic_yield_per
1946 self.context = context
1947
1948 @_generative
1949 def yield_per(self, num: int) -> Self:
1950 # TODO: this throws away the iterator which may be holding
1951 # onto a chunk. the yield_per cannot be changed once any
1952 # rows have been fetched. either find a way to enforce this,
1953 # or we can't use itertools.chain and will instead have to
1954 # keep track.
1955
1956 self._yield_per = num
1957 self.iterator = itertools.chain.from_iterable(self.chunks(num))
1958 return self
1959
1960 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
1961 super()._soft_close(hard=hard, **kw)
1962 self.chunks = lambda size: [] # type: ignore
1963
1964 def _fetchmany_impl(
1965 self, size: Optional[int] = None
1966 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1967 if self.dynamic_yield_per:
1968 self.iterator = itertools.chain.from_iterable(self.chunks(size))
1969 return super()._fetchmany_impl(size=size)
1970
1971
1972class MergedResult(IteratorResult[Unpack[_Ts]]):
1973 """A :class:`_engine.Result` that is merged from any number of
1974 :class:`_engine.Result` objects.
1975
1976 Returned by the :meth:`_engine.Result.merge` method.
1977
1978 .. versionadded:: 1.4
1979
1980 """
1981
1982 closed = False
1983 rowcount: Optional[int]
1984
1985 def __init__(
1986 self,
1987 cursor_metadata: ResultMetaData,
1988 results: Sequence[Result[Unpack[_Ts]]],
1989 ):
1990 self._results = results
1991 super().__init__(
1992 cursor_metadata,
1993 itertools.chain.from_iterable(
1994 r._raw_row_iterator() for r in results
1995 ),
1996 )
1997
1998 self._unique_filter_state = results[0]._unique_filter_state
1999 self._yield_per = results[0]._yield_per
2000
2001 # going to try something w/ this in next rev
2002 self._source_supports_scalars = results[0]._source_supports_scalars
2003
2004 self._attributes = self._attributes.merge_with(
2005 *[r._attributes for r in results]
2006 )
2007
2008 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
2009 for r in self._results:
2010 r._soft_close(hard=hard, **kw)
2011 if hard:
2012 self.closed = True