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