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