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 """Hard 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` was **hard closed**
516 by explicitly calling the :meth:`close` method.
517
518 The attribute is **not** True if the :class:`_engine.Result` was only
519 **soft closed**; a "soft close" is the style of close that takes place
520 for example when the :class:`.CursorResult` is returned for a DML
521 only statement without RETURNING, or when all result rows are fetched.
522
523 .. seealso::
524
525 :attr:`.CursorResult.returns_rows` - attribute specific to
526 :class:`.CursorResult` which indicates if the result is one that
527 may return zero or more rows
528
529 """
530 raise NotImplementedError()
531
532 @_generative
533 def yield_per(self, num: int) -> Self:
534 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
535
536 This impacts the underlying behavior of the result when iterating over
537 the result object, or otherwise making use of methods such as
538 :meth:`_engine.Result.fetchone` that return one row at a time. Data
539 from the underlying cursor or other data source will be buffered up to
540 this many rows in memory, and the buffered collection will then be
541 yielded out one row at a time or as many rows are requested. Each time
542 the buffer clears, it will be refreshed to this many rows or as many
543 rows remain if fewer remain.
544
545 The :meth:`_engine.Result.yield_per` method is generally used in
546 conjunction with the
547 :paramref:`_engine.Connection.execution_options.stream_results`
548 execution option, which will allow the database dialect in use to make
549 use of a server side cursor, if the DBAPI supports a specific "server
550 side cursor" mode separate from its default mode of operation.
551
552 .. tip::
553
554 Consider using the
555 :paramref:`_engine.Connection.execution_options.yield_per`
556 execution option, which will simultaneously set
557 :paramref:`_engine.Connection.execution_options.stream_results`
558 to ensure the use of server side cursors, as well as automatically
559 invoke the :meth:`_engine.Result.yield_per` method to establish
560 a fixed row buffer size at once.
561
562 The :paramref:`_engine.Connection.execution_options.yield_per`
563 execution option is available for ORM operations, with
564 :class:`_orm.Session`-oriented use described at
565 :ref:`orm_queryguide_yield_per`. The Core-only version which works
566 with :class:`_engine.Connection` is new as of SQLAlchemy 1.4.40.
567
568 .. versionadded:: 1.4
569
570 :param num: number of rows to fetch each time the buffer is refilled.
571 If set to a value below 1, fetches all rows for the next buffer.
572
573 .. seealso::
574
575 :ref:`engine_stream_results` - describes Core behavior for
576 :meth:`_engine.Result.yield_per`
577
578 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
579
580 """
581 self._yield_per = num
582 return self
583
584 @_generative
585 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
586 """Apply unique filtering to the objects returned by this
587 :class:`_engine.Result`.
588
589 When this filter is applied with no arguments, the rows or objects
590 returned will filtered such that each row is returned uniquely. The
591 algorithm used to determine this uniqueness is by default the Python
592 hashing identity of the whole tuple. In some cases a specialized
593 per-entity hashing scheme may be used, such as when using the ORM, a
594 scheme is applied which works against the primary key identity of
595 returned objects.
596
597 The unique filter is applied **after all other filters**, which means
598 if the columns returned have been refined using a method such as the
599 :meth:`_engine.Result.columns` or :meth:`_engine.Result.scalars`
600 method, the uniquing is applied to **only the column or columns
601 returned**. This occurs regardless of the order in which these
602 methods have been called upon the :class:`_engine.Result` object.
603
604 The unique filter also changes the calculus used for methods like
605 :meth:`_engine.Result.fetchmany` and :meth:`_engine.Result.partitions`.
606 When using :meth:`_engine.Result.unique`, these methods will continue
607 to yield the number of rows or objects requested, after uniquing
608 has been applied. However, this necessarily impacts the buffering
609 behavior of the underlying cursor or datasource, such that multiple
610 underlying calls to ``cursor.fetchmany()`` may be necessary in order
611 to accumulate enough objects in order to provide a unique collection
612 of the requested size.
613
614 :param strategy: a callable that will be applied to rows or objects
615 being iterated, which should return an object that represents the
616 unique value of the row. A Python ``set()`` is used to store
617 these identities. If not passed, a default uniqueness strategy
618 is used which may have been assembled by the source of this
619 :class:`_engine.Result` object.
620
621 """
622 self._unique_filter_state = (set(), strategy)
623 return self
624
625 def columns(self, *col_expressions: _KeyIndexType) -> Self:
626 r"""Establish the columns that should be returned in each row.
627
628 This method may be used to limit the columns returned as well
629 as to reorder them. The given list of expressions are normally
630 a series of integers or string key names. They may also be
631 appropriate :class:`.ColumnElement` objects which correspond to
632 a given statement construct.
633
634 .. versionchanged:: 2.0 Due to a bug in 1.4, the
635 :meth:`_engine.Result.columns` method had an incorrect behavior
636 where calling upon the method with just one index would cause the
637 :class:`_engine.Result` object to yield scalar values rather than
638 :class:`_engine.Row` objects. In version 2.0, this behavior
639 has been corrected such that calling upon
640 :meth:`_engine.Result.columns` with a single index will
641 produce a :class:`_engine.Result` object that continues
642 to yield :class:`_engine.Row` objects, which include
643 only a single column.
644
645 E.g.::
646
647 statement = select(table.c.x, table.c.y, table.c.z)
648 result = connection.execute(statement)
649
650 for z, y in result.columns("z", "y"):
651 ...
652
653 Example of using the column objects from the statement itself::
654
655 for z, y in result.columns(
656 statement.selected_columns.c.z, statement.selected_columns.c.y
657 ):
658 ...
659
660 .. versionadded:: 1.4
661
662 :param \*col_expressions: indicates columns to be returned. Elements
663 may be integer row indexes, string column names, or appropriate
664 :class:`.ColumnElement` objects corresponding to a select construct.
665
666 :return: this :class:`_engine.Result` object with the modifications
667 given.
668
669 """
670 return self._column_slices(col_expressions)
671
672 @overload
673 def scalars(self: Result[_T, Unpack[TupleAny]]) -> ScalarResult[_T]: ...
674
675 @overload
676 def scalars(
677 self: Result[_T, Unpack[TupleAny]], index: Literal[0]
678 ) -> ScalarResult[_T]: ...
679
680 @overload
681 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]: ...
682
683 def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]:
684 """Return a :class:`_engine.ScalarResult` filtering object which
685 will return single elements rather than :class:`_row.Row` objects.
686
687 E.g.::
688
689 >>> result = conn.execute(text("select int_id from table"))
690 >>> result.scalars().all()
691 [1, 2, 3]
692
693 When results are fetched from the :class:`_engine.ScalarResult`
694 filtering object, the single column-row that would be returned by the
695 :class:`_engine.Result` is instead returned as the column's value.
696
697 .. versionadded:: 1.4
698
699 :param index: integer or row key indicating the column to be fetched
700 from each row, defaults to ``0`` indicating the first column.
701
702 :return: a new :class:`_engine.ScalarResult` filtering object referring
703 to this :class:`_engine.Result` object.
704
705 """
706 return ScalarResult(self, index)
707
708 def _getter(
709 self, key: _KeyIndexType, raiseerr: bool = True
710 ) -> Optional[Callable[[Row[Unpack[TupleAny]]], Any]]:
711 """return a callable that will retrieve the given key from a
712 :class:`_engine.Row`.
713
714 """
715 if self._source_supports_scalars:
716 raise NotImplementedError(
717 "can't use this function in 'only scalars' mode"
718 )
719 return self._metadata._getter(key, raiseerr)
720
721 def _tuple_getter(self, keys: Sequence[_KeyIndexType]) -> _TupleGetterType:
722 """return a callable that will retrieve the given keys from a
723 :class:`_engine.Row`.
724
725 """
726 if self._source_supports_scalars:
727 raise NotImplementedError(
728 "can't use this function in 'only scalars' mode"
729 )
730 return self._metadata._row_as_tuple_getter(keys)
731
732 def mappings(self) -> MappingResult:
733 """Apply a mappings filter to returned rows, returning an instance of
734 :class:`_engine.MappingResult`.
735
736 When this filter is applied, fetching rows will return
737 :class:`_engine.RowMapping` objects instead of :class:`_engine.Row`
738 objects.
739
740 .. versionadded:: 1.4
741
742 :return: a new :class:`_engine.MappingResult` filtering object
743 referring to this :class:`_engine.Result` object.
744
745 """
746
747 return MappingResult(self)
748
749 @property
750 @deprecated(
751 "2.1.0",
752 "The :attr:`.Result.t` method is deprecated, :class:`.Row` "
753 "now behaves like a tuple and can unpack types directly.",
754 )
755 def t(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
756 """Apply a "typed tuple" typing filter to returned rows.
757
758 The :attr:`_engine.Result.t` attribute is a synonym for
759 calling the :meth:`_engine.Result.tuples` method.
760
761 .. versionadded:: 2.0
762
763 .. seealso::
764
765 :ref:`change_10635` - describes a migration path from this
766 workaround for SQLAlchemy 2.1.
767
768 """
769 return self # type: ignore
770
771 @deprecated(
772 "2.1.0",
773 "The :meth:`.Result.tuples` method is deprecated, :class:`.Row` "
774 "now behaves like a tuple and can unpack types directly.",
775 )
776 def tuples(self) -> TupleResult[Tuple[Unpack[_Ts]]]:
777 """Apply a "typed tuple" typing filter to returned rows.
778
779 This method returns the same :class:`_engine.Result` object
780 at runtime,
781 however annotates as returning a :class:`_engine.TupleResult` object
782 that will indicate to :pep:`484` typing tools that plain typed
783 ``Tuple`` instances are returned rather than rows. This allows
784 tuple unpacking and ``__getitem__`` access of :class:`_engine.Row`
785 objects to by typed, for those cases where the statement invoked
786 itself included typing information.
787
788 .. versionadded:: 2.0
789
790 :return: the :class:`_engine.TupleResult` type at typing time.
791
792 .. seealso::
793
794 :ref:`change_10635` - describes a migration path from this
795 workaround for SQLAlchemy 2.1.
796
797 :attr:`_engine.Result.t` - shorter synonym
798
799 :attr:`_engine.Row._t` - :class:`_engine.Row` version
800
801 """
802
803 return self # type: ignore
804
805 def _raw_row_iterator(self) -> Iterator[_RowData]:
806 """Return a safe iterator that yields raw row data.
807
808 This is used by the :meth:`_engine.Result.merge` method
809 to merge multiple compatible results together.
810
811 """
812 raise NotImplementedError()
813
814 def __iter__(self) -> Iterator[Row[Unpack[_Ts]]]:
815 return self._iter_impl()
816
817 def __next__(self) -> Row[Unpack[_Ts]]:
818 return self._next_impl()
819
820 def partitions(
821 self, size: Optional[int] = None
822 ) -> Iterator[Sequence[Row[Unpack[_Ts]]]]:
823 """Iterate through sub-lists of rows of the size given.
824
825 Each list will be of the size given, excluding the last list to
826 be yielded, which may have a small number of rows. No empty
827 lists will be yielded.
828
829 The result object is automatically closed when the iterator
830 is fully consumed.
831
832 Note that the backend driver will usually buffer the entire result
833 ahead of time unless the
834 :paramref:`.Connection.execution_options.stream_results` execution
835 option is used indicating that the driver should not pre-buffer
836 results, if possible. Not all drivers support this option and
837 the option is silently ignored for those who do not.
838
839 When using the ORM, the :meth:`_engine.Result.partitions` method
840 is typically more effective from a memory perspective when it is
841 combined with use of the
842 :ref:`yield_per execution option <orm_queryguide_yield_per>`,
843 which instructs both the DBAPI driver to use server side cursors,
844 if available, as well as instructs the ORM loading internals to only
845 build a certain amount of ORM objects from a result at a time before
846 yielding them out.
847
848 .. versionadded:: 1.4
849
850 :param size: indicate the maximum number of rows to be present
851 in each list yielded. If None, makes use of the value set by
852 the :meth:`_engine.Result.yield_per`, method, if it were called,
853 or the :paramref:`_engine.Connection.execution_options.yield_per`
854 execution option, which is equivalent in this regard. If
855 yield_per weren't set, it makes use of the
856 :meth:`_engine.Result.fetchmany` default, which may be backend
857 specific and not well defined.
858
859 :return: iterator of lists
860
861 .. seealso::
862
863 :ref:`engine_stream_results`
864
865 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
866
867 """
868
869 getter = self._manyrow_getter
870
871 while True:
872 partition = getter(self, size)
873 if partition:
874 yield partition
875 else:
876 break
877
878 def fetchall(self) -> Sequence[Row[Unpack[_Ts]]]:
879 """A synonym for the :meth:`_engine.Result.all` method."""
880
881 return self._allrows()
882
883 def fetchone(self) -> Optional[Row[Unpack[_Ts]]]:
884 """Fetch one row.
885
886 When all rows are exhausted, returns None.
887
888 This method is provided for backwards compatibility with
889 SQLAlchemy 1.x.x.
890
891 To fetch the first row of a result only, use the
892 :meth:`_engine.Result.first` method. To iterate through all
893 rows, iterate the :class:`_engine.Result` object directly.
894
895 :return: a :class:`_engine.Row` object if no filters are applied,
896 or ``None`` if no rows remain.
897
898 """
899 row = self._onerow_getter(self)
900 if row is _NO_ROW:
901 return None
902 else:
903 return row
904
905 def fetchmany(
906 self, size: Optional[int] = None
907 ) -> Sequence[Row[Unpack[_Ts]]]:
908 """Fetch many rows.
909
910 When all rows are exhausted, returns an empty sequence.
911
912 This method is provided for backwards compatibility with
913 SQLAlchemy 1.x.x.
914
915 To fetch rows in groups, use the :meth:`_engine.Result.partitions`
916 method.
917
918 :return: a sequence of :class:`_engine.Row` objects.
919
920 .. seealso::
921
922 :meth:`_engine.Result.partitions`
923
924 """
925
926 return self._manyrow_getter(self, size)
927
928 def all(self) -> Sequence[Row[Unpack[_Ts]]]:
929 """Return all rows in a sequence.
930
931 Closes the result set after invocation. Subsequent invocations
932 will return an empty sequence.
933
934 .. versionadded:: 1.4
935
936 :return: a sequence of :class:`_engine.Row` objects.
937
938 .. seealso::
939
940 :ref:`engine_stream_results` - How to stream a large result set
941 without loading it completely in python.
942
943 """
944
945 return self._allrows()
946
947 def first(self) -> Optional[Row[Unpack[_Ts]]]:
948 """Fetch the first row or ``None`` if no row is present.
949
950 Closes the result set and discards remaining rows.
951
952 .. note:: This method returns one **row**, e.g. tuple, by default.
953 To return exactly one single scalar value, that is, the first
954 column of the first row, use the
955 :meth:`_engine.Result.scalar` method,
956 or combine :meth:`_engine.Result.scalars` and
957 :meth:`_engine.Result.first`.
958
959 Additionally, in contrast to the behavior of the legacy ORM
960 :meth:`_orm.Query.first` method, **no limit is applied** to the
961 SQL query which was invoked to produce this
962 :class:`_engine.Result`;
963 for a DBAPI driver that buffers results in memory before yielding
964 rows, all rows will be sent to the Python process and all but
965 the first row will be discarded.
966
967 .. seealso::
968
969 :ref:`migration_20_unify_select`
970
971 :return: a :class:`_engine.Row` object, or None
972 if no rows remain.
973
974 .. seealso::
975
976 :meth:`_engine.Result.scalar`
977
978 :meth:`_engine.Result.one`
979
980 """
981
982 return self._only_one_row(
983 raise_for_second_row=False, raise_for_none=False, scalar=False
984 )
985
986 def one_or_none(self) -> Optional[Row[Unpack[_Ts]]]:
987 """Return at most one result or raise an exception.
988
989 Returns ``None`` if the result has no rows.
990 Raises :class:`.MultipleResultsFound`
991 if multiple rows are returned.
992
993 .. versionadded:: 1.4
994
995 :return: The first :class:`_engine.Row` or ``None`` if no row
996 is available.
997
998 :raises: :class:`.MultipleResultsFound`
999
1000 .. seealso::
1001
1002 :meth:`_engine.Result.first`
1003
1004 :meth:`_engine.Result.one`
1005
1006 """
1007 return self._only_one_row(
1008 raise_for_second_row=True, raise_for_none=False, scalar=False
1009 )
1010
1011 def scalar_one(self: Result[_T, Unpack[TupleAny]]) -> _T:
1012 """Return exactly one scalar result or raise an exception.
1013
1014 This is equivalent to calling :meth:`_engine.Result.scalars` and
1015 then :meth:`_engine.ScalarResult.one`.
1016
1017 .. seealso::
1018
1019 :meth:`_engine.ScalarResult.one`
1020
1021 :meth:`_engine.Result.scalars`
1022
1023 """
1024 return self._only_one_row(
1025 raise_for_second_row=True, raise_for_none=True, scalar=True
1026 )
1027
1028 def scalar_one_or_none(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1029 """Return exactly one scalar result or ``None``.
1030
1031 This is equivalent to calling :meth:`_engine.Result.scalars` and
1032 then :meth:`_engine.ScalarResult.one_or_none`.
1033
1034 .. seealso::
1035
1036 :meth:`_engine.ScalarResult.one_or_none`
1037
1038 :meth:`_engine.Result.scalars`
1039
1040 """
1041 return self._only_one_row(
1042 raise_for_second_row=True, raise_for_none=False, scalar=True
1043 )
1044
1045 def one(self) -> Row[Unpack[_Ts]]:
1046 """Return exactly one row or raise an exception.
1047
1048 Raises :class:`_exc.NoResultFound` if the result returns no
1049 rows, or :class:`_exc.MultipleResultsFound` if multiple rows
1050 would be returned.
1051
1052 .. note:: This method returns one **row**, e.g. tuple, by default.
1053 To return exactly one single scalar value, that is, the first
1054 column of the first row, use the
1055 :meth:`_engine.Result.scalar_one` method, or combine
1056 :meth:`_engine.Result.scalars` and
1057 :meth:`_engine.Result.one`.
1058
1059 .. versionadded:: 1.4
1060
1061 :return: The first :class:`_engine.Row`.
1062
1063 :raises: :class:`.MultipleResultsFound`, :class:`.NoResultFound`
1064
1065 .. seealso::
1066
1067 :meth:`_engine.Result.first`
1068
1069 :meth:`_engine.Result.one_or_none`
1070
1071 :meth:`_engine.Result.scalar_one`
1072
1073 """
1074 return self._only_one_row(
1075 raise_for_second_row=True, raise_for_none=True, scalar=False
1076 )
1077
1078 # special case to handle mypy issue:
1079 # https://github.com/python/mypy/issues/20651
1080 @overload
1081 def scalar(self: Result[Never, Unpack[TupleAny]]) -> Optional[Any]:
1082 pass
1083
1084 @overload
1085 def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1086 pass
1087
1088 def scalar(self: Result[_T, Unpack[TupleAny]]) -> Optional[_T]:
1089 """Fetch the first column of the first row, and close the result set.
1090
1091 Returns ``None`` if there are no rows to fetch.
1092
1093 No validation is performed to test if additional rows remain.
1094
1095 After calling this method, the object is fully closed,
1096 e.g. the :meth:`_engine.CursorResult.close`
1097 method will have been called.
1098
1099 :return: a Python scalar value, or ``None`` if no rows remain.
1100
1101 """
1102 return self._only_one_row(
1103 raise_for_second_row=False, raise_for_none=False, scalar=True
1104 )
1105
1106 def freeze(self) -> FrozenResult[Unpack[_Ts]]:
1107 """Return a callable object that will produce copies of this
1108 :class:`_engine.Result` when invoked.
1109
1110 The callable object returned is an instance of
1111 :class:`_engine.FrozenResult`.
1112
1113 This is used for result set caching. The method must be called
1114 on the result when it has been unconsumed, and calling the method
1115 will consume the result fully. When the :class:`_engine.FrozenResult`
1116 is retrieved from a cache, it can be called any number of times where
1117 it will produce a new :class:`_engine.Result` object each time
1118 against its stored set of rows.
1119
1120 .. seealso::
1121
1122 :ref:`do_orm_execute_re_executing` - example usage within the
1123 ORM to implement a result-set cache.
1124
1125 """
1126
1127 return FrozenResult(self)
1128
1129 def merge(
1130 self, *others: Result[Unpack[TupleAny]]
1131 ) -> MergedResult[Unpack[TupleAny]]:
1132 """Merge this :class:`_engine.Result` with other compatible result
1133 objects.
1134
1135 The object returned is an instance of :class:`_engine.MergedResult`,
1136 which will be composed of iterators from the given result
1137 objects.
1138
1139 The new result will use the metadata from this result object.
1140 The subsequent result objects must be against an identical
1141 set of result / cursor metadata, otherwise the behavior is
1142 undefined.
1143
1144 """
1145 return MergedResult(self._metadata, (self,) + others)
1146
1147
1148class FilterResult(ResultInternal[_R]):
1149 """A wrapper for a :class:`_engine.Result` that returns objects other than
1150 :class:`_engine.Row` objects, such as dictionaries or scalar objects.
1151
1152 :class:`_engine.FilterResult` is the common base for additional result
1153 APIs including :class:`_engine.MappingResult`,
1154 :class:`_engine.ScalarResult` and :class:`_engine.AsyncResult`.
1155
1156 """
1157
1158 __slots__ = (
1159 "_real_result",
1160 "_post_creational_filter",
1161 "_metadata",
1162 "_unique_filter_state",
1163 "__dict__",
1164 )
1165
1166 _post_creational_filter: Optional[Callable[[Any], Any]]
1167
1168 _real_result: Result[Unpack[TupleAny]]
1169
1170 def __enter__(self) -> Self:
1171 return self
1172
1173 def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
1174 self._real_result.__exit__(type_, value, traceback)
1175
1176 @_generative
1177 def yield_per(self, num: int) -> Self:
1178 """Configure the row-fetching strategy to fetch ``num`` rows at a time.
1179
1180 The :meth:`_engine.FilterResult.yield_per` method is a pass through
1181 to the :meth:`_engine.Result.yield_per` method. See that method's
1182 documentation for usage notes.
1183
1184 .. versionadded:: 1.4.40 - added :meth:`_engine.FilterResult.yield_per`
1185 so that the method is available on all result set implementations
1186
1187 .. seealso::
1188
1189 :ref:`engine_stream_results` - describes Core behavior for
1190 :meth:`_engine.Result.yield_per`
1191
1192 :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1193
1194 """
1195 self._real_result = self._real_result.yield_per(num)
1196 return self
1197
1198 def _soft_close(self, hard: bool = False) -> None:
1199 self._real_result._soft_close(hard=hard)
1200
1201 @property
1202 def _soft_closed(self) -> bool:
1203 return self._real_result._soft_closed
1204
1205 @property
1206 def closed(self) -> bool:
1207 """Return ``True`` if this :class:`_engine.Result` being
1208 proxied by this :class:`_engine.FilterResult` was
1209 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
1210 method.
1211
1212 This is a direct proxy for the :attr:`_engine.Result.closed` attribute;
1213 see that attribute for details.
1214
1215 """
1216 return self._real_result.closed
1217
1218 def close(self) -> None:
1219 """Close this :class:`_engine.FilterResult`.
1220
1221 .. versionadded:: 1.4.43
1222
1223 """
1224 self._real_result.close()
1225
1226 @property
1227 def _attributes(self) -> Dict[Any, Any]:
1228 return self._real_result._attributes
1229
1230 def _fetchiter_impl(
1231 self,
1232 ) -> Iterator[_InterimRowType[Row[Unpack[TupleAny]]]]:
1233 return self._real_result._fetchiter_impl()
1234
1235 def _fetchone_impl(
1236 self, hard_close: bool = False
1237 ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
1238 return self._real_result._fetchone_impl(hard_close=hard_close)
1239
1240 def _fetchall_impl(
1241 self,
1242 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1243 return self._real_result._fetchall_impl()
1244
1245 def _fetchmany_impl(
1246 self, size: Optional[int] = None
1247 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1248 return self._real_result._fetchmany_impl(size=size)
1249
1250
1251class ScalarResult(FilterResult[_R]):
1252 """A wrapper for a :class:`_engine.Result` that returns scalar values
1253 rather than :class:`_row.Row` values.
1254
1255 The :class:`_engine.ScalarResult` object is acquired by calling the
1256 :meth:`_engine.Result.scalars` method.
1257
1258 A special limitation of :class:`_engine.ScalarResult` is that it has
1259 no ``fetchone()`` method; since the semantics of ``fetchone()`` are that
1260 the ``None`` value indicates no more results, this is not compatible
1261 with :class:`_engine.ScalarResult` since there is no way to distinguish
1262 between ``None`` as a row value versus ``None`` as an indicator. Use
1263 ``next(result)`` to receive values individually.
1264
1265 """
1266
1267 __slots__ = ()
1268
1269 _generate_rows = False
1270
1271 _post_creational_filter: Optional[Callable[[Any], Any]]
1272
1273 def __init__(
1274 self, real_result: Result[Unpack[TupleAny]], index: _KeyIndexType
1275 ):
1276 self._real_result = real_result
1277
1278 if real_result._source_supports_scalars:
1279 self._metadata = real_result._metadata
1280 self._post_creational_filter = None
1281 else:
1282 self._metadata = real_result._metadata._reduce([index])
1283 self._post_creational_filter = operator.itemgetter(0)
1284
1285 self._unique_filter_state = real_result._unique_filter_state
1286
1287 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1288 """Apply unique filtering to the objects returned by this
1289 :class:`_engine.ScalarResult`.
1290
1291 See :meth:`_engine.Result.unique` for usage details.
1292
1293 """
1294 self._unique_filter_state = (set(), strategy)
1295 return self
1296
1297 def partitions(self, size: Optional[int] = None) -> Iterator[Sequence[_R]]:
1298 """Iterate through sub-lists of elements of the size given.
1299
1300 Equivalent to :meth:`_engine.Result.partitions` except that
1301 scalar values, rather than :class:`_engine.Row` objects,
1302 are returned.
1303
1304 """
1305
1306 getter = self._manyrow_getter
1307
1308 while True:
1309 partition = getter(self, size)
1310 if partition:
1311 yield partition
1312 else:
1313 break
1314
1315 def fetchall(self) -> Sequence[_R]:
1316 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1317
1318 return self._allrows()
1319
1320 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1321 """Fetch many objects.
1322
1323 Equivalent to :meth:`_engine.Result.fetchmany` except that
1324 scalar values, rather than :class:`_engine.Row` objects,
1325 are returned.
1326
1327 """
1328 return self._manyrow_getter(self, size)
1329
1330 def all(self) -> Sequence[_R]:
1331 """Return all scalar values in a sequence.
1332
1333 Equivalent to :meth:`_engine.Result.all` except that
1334 scalar values, rather than :class:`_engine.Row` objects,
1335 are returned.
1336
1337 """
1338 return self._allrows()
1339
1340 def __iter__(self) -> Iterator[_R]:
1341 return self._iter_impl()
1342
1343 def __next__(self) -> _R:
1344 return self._next_impl()
1345
1346 def first(self) -> Optional[_R]:
1347 """Fetch the first object or ``None`` if no object is present.
1348
1349 Equivalent to :meth:`_engine.Result.first` except that
1350 scalar values, rather than :class:`_engine.Row` objects,
1351 are returned.
1352
1353
1354 """
1355 return self._only_one_row(
1356 raise_for_second_row=False, raise_for_none=False, scalar=False
1357 )
1358
1359 def one_or_none(self) -> Optional[_R]:
1360 """Return at most one object or raise an exception.
1361
1362 Equivalent to :meth:`_engine.Result.one_or_none` except that
1363 scalar values, rather than :class:`_engine.Row` objects,
1364 are returned.
1365
1366 """
1367 return self._only_one_row(
1368 raise_for_second_row=True, raise_for_none=False, scalar=False
1369 )
1370
1371 def one(self) -> _R:
1372 """Return exactly one object or raise an exception.
1373
1374 Equivalent to :meth:`_engine.Result.one` except that
1375 scalar values, rather than :class:`_engine.Row` objects,
1376 are returned.
1377
1378 """
1379 return self._only_one_row(
1380 raise_for_second_row=True, raise_for_none=True, scalar=False
1381 )
1382
1383
1384class TupleResult(FilterResult[_R], util.TypingOnly):
1385 """A :class:`_engine.Result` that's typed as returning plain
1386 Python tuples instead of rows.
1387
1388 Since :class:`_engine.Row` acts like a tuple in every way already,
1389 this class is a typing only class, regular :class:`_engine.Result` is
1390 still used at runtime.
1391
1392 """
1393
1394 __slots__ = ()
1395
1396 if TYPE_CHECKING:
1397
1398 def partitions(
1399 self, size: Optional[int] = None
1400 ) -> Iterator[Sequence[_R]]:
1401 """Iterate through sub-lists of elements of the size given.
1402
1403 Equivalent to :meth:`_engine.Result.partitions` except that
1404 tuple values, rather than :class:`_engine.Row` objects,
1405 are returned.
1406
1407 """
1408 ...
1409
1410 def fetchone(self) -> Optional[_R]:
1411 """Fetch one tuple.
1412
1413 Equivalent to :meth:`_engine.Result.fetchone` except that
1414 tuple values, rather than :class:`_engine.Row`
1415 objects, are returned.
1416
1417 """
1418 ...
1419
1420 def fetchall(self) -> Sequence[_R]:
1421 """A synonym for the :meth:`_engine.ScalarResult.all` method."""
1422 ...
1423
1424 def fetchmany(self, size: Optional[int] = None) -> Sequence[_R]:
1425 """Fetch many objects.
1426
1427 Equivalent to :meth:`_engine.Result.fetchmany` except that
1428 tuple values, rather than :class:`_engine.Row` objects,
1429 are returned.
1430
1431 """
1432 ...
1433
1434 def all(self) -> Sequence[_R]: # noqa: A001
1435 """Return all scalar values in a sequence.
1436
1437 Equivalent to :meth:`_engine.Result.all` except that
1438 tuple values, rather than :class:`_engine.Row` objects,
1439 are returned.
1440
1441 """
1442 ...
1443
1444 def __iter__(self) -> Iterator[_R]: ...
1445
1446 def __next__(self) -> _R: ...
1447
1448 def first(self) -> Optional[_R]:
1449 """Fetch the first object or ``None`` if no object is present.
1450
1451 Equivalent to :meth:`_engine.Result.first` except that
1452 tuple values, rather than :class:`_engine.Row` objects,
1453 are returned.
1454
1455
1456 """
1457 ...
1458
1459 def one_or_none(self) -> Optional[_R]:
1460 """Return at most one object or raise an exception.
1461
1462 Equivalent to :meth:`_engine.Result.one_or_none` except that
1463 tuple values, rather than :class:`_engine.Row` objects,
1464 are returned.
1465
1466 """
1467 ...
1468
1469 def one(self) -> _R:
1470 """Return exactly one object or raise an exception.
1471
1472 Equivalent to :meth:`_engine.Result.one` except that
1473 tuple values, rather than :class:`_engine.Row` objects,
1474 are returned.
1475
1476 """
1477 ...
1478
1479 @overload
1480 def scalar_one(self: TupleResult[Tuple[_T]]) -> _T: ...
1481
1482 @overload
1483 def scalar_one(self) -> Any: ...
1484
1485 def scalar_one(self) -> Any:
1486 """Return exactly one scalar result or raise an exception.
1487
1488 This is equivalent to calling :meth:`_engine.Result.scalars`
1489 and then :meth:`_engine.ScalarResult.one`.
1490
1491 .. seealso::
1492
1493 :meth:`_engine.ScalarResult.one`
1494
1495 :meth:`_engine.Result.scalars`
1496
1497 """
1498 ...
1499
1500 @overload
1501 def scalar_one_or_none(
1502 self: TupleResult[Tuple[_T]],
1503 ) -> Optional[_T]: ...
1504
1505 @overload
1506 def scalar_one_or_none(self) -> Optional[Any]: ...
1507
1508 def scalar_one_or_none(self) -> Optional[Any]:
1509 """Return exactly one or no scalar result.
1510
1511 This is equivalent to calling :meth:`_engine.Result.scalars`
1512 and then :meth:`_engine.ScalarResult.one_or_none`.
1513
1514 .. seealso::
1515
1516 :meth:`_engine.ScalarResult.one_or_none`
1517
1518 :meth:`_engine.Result.scalars`
1519
1520 """
1521 ...
1522
1523 @overload
1524 def scalar(self: TupleResult[Tuple[_T]]) -> Optional[_T]: ...
1525
1526 @overload
1527 def scalar(self) -> Any: ...
1528
1529 def scalar(self) -> Any:
1530 """Fetch the first column of the first row, and close the result
1531 set.
1532
1533 Returns ``None`` if there are no rows to fetch.
1534
1535 No validation is performed to test if additional rows remain.
1536
1537 After calling this method, the object is fully closed,
1538 e.g. the :meth:`_engine.CursorResult.close`
1539 method will have been called.
1540
1541 :return: a Python scalar value , or ``None`` if no rows remain.
1542
1543 """
1544 ...
1545
1546
1547class MappingResult(_WithKeys, FilterResult[RowMapping]):
1548 """A wrapper for a :class:`_engine.Result` that returns dictionary values
1549 rather than :class:`_engine.Row` values.
1550
1551 The :class:`_engine.MappingResult` object is acquired by calling the
1552 :meth:`_engine.Result.mappings` method.
1553
1554 """
1555
1556 __slots__ = ()
1557
1558 _generate_rows = True
1559
1560 _post_creational_filter = operator.attrgetter("_mapping")
1561
1562 def __init__(self, result: Result[Unpack[TupleAny]]):
1563 self._real_result = result
1564 self._unique_filter_state = result._unique_filter_state
1565 self._metadata = result._metadata
1566 if result._source_supports_scalars:
1567 self._metadata = self._metadata._reduce([0])
1568
1569 def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1570 """Apply unique filtering to the objects returned by this
1571 :class:`_engine.MappingResult`.
1572
1573 See :meth:`_engine.Result.unique` for usage details.
1574
1575 """
1576 self._unique_filter_state = (set(), strategy)
1577 return self
1578
1579 def columns(self, *col_expressions: _KeyIndexType) -> Self:
1580 """Establish the columns that should be returned in each row."""
1581 return self._column_slices(col_expressions)
1582
1583 def partitions(
1584 self, size: Optional[int] = None
1585 ) -> Iterator[Sequence[RowMapping]]:
1586 """Iterate through sub-lists of elements of the size given.
1587
1588 Equivalent to :meth:`_engine.Result.partitions` except that
1589 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1590 objects, are returned.
1591
1592 """
1593
1594 getter = self._manyrow_getter
1595
1596 while True:
1597 partition = getter(self, size)
1598 if partition:
1599 yield partition
1600 else:
1601 break
1602
1603 def fetchall(self) -> Sequence[RowMapping]:
1604 """A synonym for the :meth:`_engine.MappingResult.all` method."""
1605
1606 return self._allrows()
1607
1608 def fetchone(self) -> Optional[RowMapping]:
1609 """Fetch one object.
1610
1611 Equivalent to :meth:`_engine.Result.fetchone` except that
1612 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1613 objects, are returned.
1614
1615 """
1616
1617 row = self._onerow_getter(self)
1618 if row is _NO_ROW:
1619 return None
1620 else:
1621 return row
1622
1623 def fetchmany(self, size: Optional[int] = None) -> Sequence[RowMapping]:
1624 """Fetch many objects.
1625
1626 Equivalent to :meth:`_engine.Result.fetchmany` except that
1627 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1628 objects, are returned.
1629
1630 """
1631
1632 return self._manyrow_getter(self, size)
1633
1634 def all(self) -> Sequence[RowMapping]:
1635 """Return all scalar values in a sequence.
1636
1637 Equivalent to :meth:`_engine.Result.all` except that
1638 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1639 objects, are returned.
1640
1641 """
1642
1643 return self._allrows()
1644
1645 def __iter__(self) -> Iterator[RowMapping]:
1646 return self._iter_impl()
1647
1648 def __next__(self) -> RowMapping:
1649 return self._next_impl()
1650
1651 def first(self) -> Optional[RowMapping]:
1652 """Fetch the first object or ``None`` if no object is present.
1653
1654 Equivalent to :meth:`_engine.Result.first` except that
1655 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1656 objects, are returned.
1657
1658
1659 """
1660 return self._only_one_row(
1661 raise_for_second_row=False, raise_for_none=False, scalar=False
1662 )
1663
1664 def one_or_none(self) -> Optional[RowMapping]:
1665 """Return at most one object or raise an exception.
1666
1667 Equivalent to :meth:`_engine.Result.one_or_none` except that
1668 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1669 objects, are returned.
1670
1671 """
1672 return self._only_one_row(
1673 raise_for_second_row=True, raise_for_none=False, scalar=False
1674 )
1675
1676 def one(self) -> RowMapping:
1677 """Return exactly one object or raise an exception.
1678
1679 Equivalent to :meth:`_engine.Result.one` except that
1680 :class:`_engine.RowMapping` values, rather than :class:`_engine.Row`
1681 objects, are returned.
1682
1683 """
1684 return self._only_one_row(
1685 raise_for_second_row=True, raise_for_none=True, scalar=False
1686 )
1687
1688
1689class FrozenResult(Generic[Unpack[_Ts]]):
1690 """Represents a :class:`_engine.Result` object in a "frozen" state suitable
1691 for caching.
1692
1693 The :class:`_engine.FrozenResult` object is returned from the
1694 :meth:`_engine.Result.freeze` method of any :class:`_engine.Result`
1695 object.
1696
1697 A new iterable :class:`_engine.Result` object is generated from a fixed
1698 set of data each time the :class:`_engine.FrozenResult` is invoked as
1699 a callable::
1700
1701
1702 result = connection.execute(query)
1703
1704 frozen = result.freeze()
1705
1706 unfrozen_result_one = frozen()
1707
1708 for row in unfrozen_result_one:
1709 print(row)
1710
1711 unfrozen_result_two = frozen()
1712 rows = unfrozen_result_two.all()
1713
1714 # ... etc
1715
1716 .. versionadded:: 1.4
1717
1718 .. seealso::
1719
1720 :ref:`do_orm_execute_re_executing` - example usage within the
1721 ORM to implement a result-set cache.
1722
1723 :func:`_orm.loading.merge_frozen_result` - ORM function to merge
1724 a frozen result back into a :class:`_orm.Session`.
1725
1726 """
1727
1728 data: Sequence[Any]
1729
1730 def __init__(self, result: Result[Unpack[_Ts]]):
1731 self.metadata = result._metadata._for_freeze()
1732 self._source_supports_scalars = result._source_supports_scalars
1733 self._attributes = result._attributes
1734
1735 if self._source_supports_scalars:
1736 self.data = list(result._raw_row_iterator())
1737 else:
1738 self.data = result.fetchall()
1739
1740 def _rewrite_rows(self) -> Sequence[Sequence[Any]]:
1741 # used only by the orm fn merge_frozen_result
1742 if self._source_supports_scalars:
1743 return [[elem] for elem in self.data]
1744 else:
1745 return [list(row) for row in self.data]
1746
1747 def with_new_rows(
1748 self, tuple_data: Sequence[Row[Unpack[_Ts]]]
1749 ) -> FrozenResult[Unpack[_Ts]]:
1750 fr = FrozenResult.__new__(FrozenResult)
1751 fr.metadata = self.metadata
1752 fr._attributes = self._attributes
1753 fr._source_supports_scalars = self._source_supports_scalars
1754
1755 if self._source_supports_scalars:
1756 fr.data = [d[0] for d in tuple_data] # type: ignore[misc]
1757 else:
1758 fr.data = tuple_data
1759 return fr
1760
1761 def __call__(self) -> Result[Unpack[_Ts]]:
1762 result: IteratorResult[Unpack[_Ts]] = IteratorResult(
1763 self.metadata, iter(self.data)
1764 )
1765 result._attributes = self._attributes
1766 result._source_supports_scalars = self._source_supports_scalars
1767 return result
1768
1769
1770class IteratorResult(Result[Unpack[_Ts]]):
1771 """A :class:`_engine.Result` that gets data from a Python iterator of
1772 :class:`_engine.Row` objects or similar row-like data.
1773
1774 .. versionadded:: 1.4
1775
1776 """
1777
1778 _hard_closed = False
1779 _soft_closed = False
1780
1781 def __init__(
1782 self,
1783 cursor_metadata: ResultMetaData,
1784 iterator: Iterator[_InterimSupportsScalarsRowType],
1785 raw: Optional[Result[Any]] = None,
1786 _source_supports_scalars: bool = False,
1787 ):
1788 self._metadata = cursor_metadata
1789 self.iterator = iterator
1790 self.raw = raw
1791 self._source_supports_scalars = _source_supports_scalars
1792
1793 @property
1794 def closed(self) -> bool:
1795 """Return ``True`` if this :class:`_engine.IteratorResult` was
1796 **hard closed** by explicitly calling the :meth:`_engine.Result.close`
1797 method.
1798
1799 The attribute is **not** True if the :class:`_engine.Result` was only
1800 **soft closed**; a "soft close" is the style of close that takes place
1801 for example when the :class:`.CursorResult` is returned for a DML
1802 only statement without RETURNING, or when all result rows are fetched.
1803
1804 .. seealso::
1805
1806 :attr:`.CursorResult.returns_rows` - attribute specific to
1807 :class:`.CursorResult` which indicates if the result is one that
1808 may return zero or more rows
1809
1810 """
1811 return self._hard_closed
1812
1813 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
1814 if hard:
1815 self._hard_closed = True
1816 if self.raw is not None:
1817 self.raw._soft_close(hard=hard, **kw)
1818 self.iterator = iter([])
1819 self._reset_memoizations()
1820 self._soft_closed = True
1821
1822 def _raise_hard_closed(self) -> NoReturn:
1823 raise exc.ResourceClosedError("This result object is closed.")
1824
1825 def _raw_row_iterator(self) -> Iterator[_RowData]:
1826 return self.iterator
1827
1828 def _fetchiter_impl(self) -> Iterator[_InterimSupportsScalarsRowType]:
1829 if self._hard_closed:
1830 self._raise_hard_closed()
1831 return self.iterator
1832
1833 def _fetchone_impl(
1834 self, hard_close: bool = False
1835 ) -> Optional[_InterimRowType[Row[Unpack[TupleAny]]]]:
1836 if self._hard_closed:
1837 self._raise_hard_closed()
1838
1839 row = next(self.iterator, _NO_ROW)
1840 if row is _NO_ROW:
1841 self._soft_close(hard=hard_close)
1842 return None
1843 else:
1844 return row
1845
1846 def _fetchall_impl(
1847 self,
1848 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1849 if self._hard_closed:
1850 self._raise_hard_closed()
1851 try:
1852 return list(self.iterator)
1853 finally:
1854 self._soft_close()
1855
1856 def _fetchmany_impl(
1857 self, size: Optional[int] = None
1858 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1859 if self._hard_closed:
1860 self._raise_hard_closed()
1861
1862 return list(itertools.islice(self.iterator, 0, size))
1863
1864
1865def null_result() -> IteratorResult[Any]:
1866 return IteratorResult(SimpleResultMetaData([]), iter([]))
1867
1868
1869class ChunkedIteratorResult(IteratorResult[Unpack[_Ts]]):
1870 """An :class:`_engine.IteratorResult` that works from an
1871 iterator-producing callable.
1872
1873 The given ``chunks`` argument is a function that is given a number of rows
1874 to return in each chunk, or ``None`` for all rows. The function should
1875 then return an un-consumed iterator of lists, each list of the requested
1876 size.
1877
1878 The function can be called at any time again, in which case it should
1879 continue from the same result set but adjust the chunk size as given.
1880
1881 .. versionadded:: 1.4
1882
1883 """
1884
1885 def __init__(
1886 self,
1887 cursor_metadata: ResultMetaData,
1888 chunks: Callable[
1889 [Optional[int]], Iterator[Sequence[_InterimRowType[_R]]]
1890 ],
1891 source_supports_scalars: bool = False,
1892 raw: Optional[Result[Any]] = None,
1893 dynamic_yield_per: bool = False,
1894 ):
1895 self._metadata = cursor_metadata
1896 self.chunks = chunks
1897 self._source_supports_scalars = source_supports_scalars
1898 self.raw = raw
1899 self.iterator = itertools.chain.from_iterable(self.chunks(None))
1900 self.dynamic_yield_per = dynamic_yield_per
1901
1902 @_generative
1903 def yield_per(self, num: int) -> Self:
1904 # TODO: this throws away the iterator which may be holding
1905 # onto a chunk. the yield_per cannot be changed once any
1906 # rows have been fetched. either find a way to enforce this,
1907 # or we can't use itertools.chain and will instead have to
1908 # keep track.
1909
1910 self._yield_per = num
1911 self.iterator = itertools.chain.from_iterable(self.chunks(num))
1912 return self
1913
1914 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
1915 super()._soft_close(hard=hard, **kw)
1916 self.chunks = lambda size: [] # type: ignore
1917
1918 def _fetchmany_impl(
1919 self, size: Optional[int] = None
1920 ) -> List[_InterimRowType[Row[Unpack[TupleAny]]]]:
1921 if self.dynamic_yield_per:
1922 self.iterator = itertools.chain.from_iterable(self.chunks(size))
1923 return super()._fetchmany_impl(size=size)
1924
1925
1926class MergedResult(IteratorResult[Unpack[_Ts]]):
1927 """A :class:`_engine.Result` that is merged from any number of
1928 :class:`_engine.Result` objects.
1929
1930 Returned by the :meth:`_engine.Result.merge` method.
1931
1932 .. versionadded:: 1.4
1933
1934 """
1935
1936 closed = False
1937 rowcount: Optional[int]
1938
1939 def __init__(
1940 self,
1941 cursor_metadata: ResultMetaData,
1942 results: Sequence[Result[Unpack[_Ts]]],
1943 ):
1944 self._results = results
1945 super().__init__(
1946 cursor_metadata,
1947 itertools.chain.from_iterable(
1948 r._raw_row_iterator() for r in results
1949 ),
1950 )
1951
1952 self._unique_filter_state = results[0]._unique_filter_state
1953 self._yield_per = results[0]._yield_per
1954
1955 # going to try something w/ this in next rev
1956 self._source_supports_scalars = results[0]._source_supports_scalars
1957
1958 self._attributes = self._attributes.merge_with(
1959 *[r._attributes for r in results]
1960 )
1961
1962 def _soft_close(self, hard: bool = False, **kw: Any) -> None:
1963 for r in self._results:
1964 r._soft_close(hard=hard, **kw)
1965 if hard:
1966 self.closed = True