Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
result.py2383 linesDownload Raw Back to engine
1# engine/result.py
2# Copyright (C) 2005-2024 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
12from enum import Enum
13import functools
14import itertools
15import operator
16import typing
17from typing import Any
18from typing import Callable
19from typing import cast
20from typing import Dict
21from typing import Generic
22from typing import Iterable
23from typing import Iterator
24from typing import List
25from typing import Mapping
26from typing import NoReturn
27from typing import Optional
28from typing import overload
29from typing import Sequence
30from typing import Set
31from typing import Tuple
32from typing import TYPE_CHECKING
33from typing import TypeVar
34from typing import Union
35
36from .row import Row
37from .row import RowMapping
38from .. import exc
39from .. import util
40from ..sql.base import _generative
41from ..sql.base import HasMemoized
42from ..sql.base import InPlaceGenerative
43from ..util import HasMemoized_ro_memoized_attribute
44from ..util import NONE_SET
45from ..util._has_cy import HAS_CYEXTENSION
46from ..util.typing import Literal
47from ..util.typing import Self
48
49if typing.TYPE_CHECKING or not HAS_CYEXTENSION:
50    from ._py_row import tuplegetter as tuplegetter
51else:
52    from sqlalchemy.cyextension.resultproxy import tuplegetter as tuplegetter
53
54if typing.TYPE_CHECKING:
55    from ..sql.schema import Column
56    from ..sql.type_api import _ResultProcessorType
57
58_KeyType = Union[str, "Column[Any]"]
59_KeyIndexType = Union[str, "Column[Any]", int]
60
61# is overridden in cursor using _CursorKeyMapRecType
62_KeyMapRecType = Any
63
64_KeyMapType = Mapping[_KeyType, _KeyMapRecType]
65
66
67_RowData = Union[Row[Any], RowMapping, Any]
68"""A generic form of "row" that accommodates for the different kinds of
69"rows" that different result objects return, including row, row mapping, and
70scalar values"""
71
72_RawRowType = Tuple[Any, ...]
73"""represents the kind of row we get from a DBAPI cursor"""
74
75_R = TypeVar("_R", bound=_RowData)
76_T = TypeVar("_T", bound=Any)
77_TP = TypeVar("_TP", bound=Tuple[Any, ...])
78
79_InterimRowType = Union[_R, _RawRowType]
80"""a catchall "anything" kind of return type that can be applied
81across all the result types
82
83"""
84
85_InterimSupportsScalarsRowType = Union[Row[Any], Any]
86
87_ProcessorsType = Sequence[Optional["_ResultProcessorType[Any]"]]
88_TupleGetterType = Callable[[Sequence[Any]], Sequence[Any]]
89_UniqueFilterType = Callable[[Any], Any]
90_UniqueFilterStateType = Tuple[Set[Any], Optional[_UniqueFilterType]]
91
92
93class ResultMetaData:
94    """Base for metadata about result rows."""
95
96    __slots__ = ()
97
98    _tuplefilter: Optional[_TupleGetterType] = None
99    _translated_indexes: Optional[Sequence[int]] = None
100    _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None
101    _keymap: _KeyMapType
102    _keys: Sequence[str]
103    _processors: Optional[_ProcessorsType]
104    _key_to_index: Mapping[_KeyType, int]
105
106    @property
107    def keys(self) -> RMKeyView:
108        return RMKeyView(self)
109
110    def _has_key(self, key: object) -> bool:
111        raise NotImplementedError()
112
113    def _for_freeze(self) -> ResultMetaData:
114        raise NotImplementedError()
115
116    @overload
117    def _key_fallback(
118        self, key: Any, err: Optional[Exception], raiseerr: Literal[True] = ...
119    ) -> NoReturn: ...
120
121    @overload
122    def _key_fallback(
123        self,
124        key: Any,
125        err: Optional[Exception],
126        raiseerr: Literal[False] = ...,
127    ) -> None: ...
128
129    @overload
130    def _key_fallback(
131        self, key: Any, err: Optional[Exception], raiseerr: bool = ...
132    ) -> Optional[NoReturn]: ...
133
134    def _key_fallback(
135        self, key: Any, err: Optional[Exception], raiseerr: bool = True
136    ) -> Optional[NoReturn]:
137        assert raiseerr
138        raise KeyError(key) from err
139
140    def _raise_for_ambiguous_column_name(
141        self, rec: _KeyMapRecType
142    ) -> NoReturn:
143        raise NotImplementedError(
144            "ambiguous column name logic is implemented for "
145            "CursorResultMetaData"
146        )
147
148    def _index_for_key(
149        self, key: _KeyIndexType, raiseerr: bool
150    ) -> Optional[int]:
151        raise NotImplementedError()
152
153    def _indexes_for_keys(
154        self, keys: Sequence[_KeyIndexType]
155    ) -> Sequence[int]:
156        raise NotImplementedError()
157
158    def _metadata_for_keys(
159        self, keys: Sequence[_KeyIndexType]
160    ) -> Iterator[_KeyMapRecType]:
161        raise NotImplementedError()
162
163    def _reduce(self, keys: Sequence[_KeyIndexType]) -> ResultMetaData:
164        raise NotImplementedError()
165
166    def _getter(
167        self, key: Any, raiseerr: bool = True
168    ) -> Optional[Callable[[Row[Any]], Any]]:
169        index = self._index_for_key(key, raiseerr)
170
171        if index is not None:
172            return operator.itemgetter(index)
173        else:
174            return None
175
176    def _row_as_tuple_getter(
177        self, keys: Sequence[_KeyIndexType]
178    ) -> _TupleGetterType:
179        indexes = self._indexes_for_keys(keys)
180        return tuplegetter(*indexes)
181
182    def _make_key_to_index(
183        self, keymap: Mapping[_KeyType, Sequence[Any]], index: int
184    ) -> Mapping[_KeyType, int]:
185        return {
186            key: rec[index]
187            for key, rec in keymap.items()
188            if rec[index] is not None
189        }
190
191    def _key_not_found(self, key: Any, attr_error: bool) -> NoReturn:
192        if key in self._keymap:
193            # the index must be none in this case
194            self._raise_for_ambiguous_column_name(self._keymap[key])
195        else:
196            # unknown key
197            if attr_error:
198                try:
199                    self._key_fallback(key, None)
200                except KeyError as ke:
201                    raise AttributeError(ke.args[0]) from ke
202            else:
203                self._key_fallback(key, None)
204
205    @property
206    def _effective_processors(self) -> Optional[_ProcessorsType]:
207        if not self._processors or NONE_SET.issuperset(self._processors):
208            return None
209        else:
210            return self._processors
211
212
213class RMKeyView(typing.KeysView[Any]):
214    __slots__ = ("_parent", "_keys")
215
216    _parent: ResultMetaData
217    _keys: Sequence[str]
218
219    def __init__(self, parent: ResultMetaData):
220        self._parent = parent
221        self._keys = [k for k in parent._keys if k is not None]
222
223    def __len__(self) -> int:
224        return len(self._keys)
225
226    def __repr__(self) -> str:
227        return "{0.__class__.__name__}({0._keys!r})".format(self)
228
229    def __iter__(self) -> Iterator[str]:
230        return iter(self._keys)
231
232    def __contains__(self, item: Any) -> bool:
233        if isinstance(item, int):
234            return False
235
236        # note this also includes special key fallback behaviors
237        # which also don't seem to be tested in test_resultset right now
238        return self._parent._has_key(item)
239
240    def __eq__(self, other: Any) -> bool:
241        return list(other) == list(self)
242
243    def __ne__(self, other: Any) -> bool:
244        return list(other) != list(self)
245
246
247class SimpleResultMetaData(ResultMetaData):
248    """result metadata for in-memory collections."""
249
250    __slots__ = (
251        "_keys",
252        "_keymap",
253        "_processors",
254        "_tuplefilter",
255        "_translated_indexes",
256        "_unique_filters",
257        "_key_to_index",
258    )
259
260    _keys: Sequence[str]
261
262    def __init__(
263        self,
264        keys: Sequence[str],
265        extra: Optional[Sequence[Any]] = None,
266        _processors: Optional[_ProcessorsType] = None,
267        _tuplefilter: Optional[_TupleGetterType] = None,
268        _translated_indexes: Optional[Sequence[int]] = None,
269        _unique_filters: Optional[Sequence[Callable[[Any], Any]]] = None,
270    ):
271        self._keys = list(keys)
272        self._tuplefilter = _tuplefilter
273        self._translated_indexes = _translated_indexes
274        self._unique_filters = _unique_filters
275        if extra:
276            recs_names = [
277                (
278                    (name,) + (extras if extras else ()),
279                    (index, name, extras),
280                )
281                for index, (name, extras) in enumerate(zip(self._keys, extra))
282            ]
283        else:
284            recs_names = [
285                ((name,), (index, name, ()))
286                for index, name in enumerate(self._keys)
287            ]
288
289        self._keymap = {key: rec for keys, rec in recs_names for key in keys}
290
291        self._processors = _processors
292
293        self._key_to_index = self._make_key_to_index(self._keymap, 0)
294
295    def _has_key(self, key: object) -> bool:
296        return key in self._keymap
297
298    def _for_freeze(self) -> ResultMetaData:
299        unique_filters = self._unique_filters
300        if unique_filters and self._tuplefilter:
301            unique_filters = self._tuplefilter(unique_filters)
302
303        # TODO: are we freezing the result with or without uniqueness
304        # applied?
305        return SimpleResultMetaData(
306            self._keys,
307            extra=[self._keymap[key][2] for key in self._keys],
308            _unique_filters=unique_filters,
309        )
310
311    def __getstate__(self) -> Dict[str, Any]:
312        return {
313            "_keys": self._keys,
314            "_translated_indexes": self._translated_indexes,
315        }
316
317    def __setstate__(self, state: Dict[str, Any]) -> None:
318        if state["_translated_indexes"]:
319            _translated_indexes = state["_translated_indexes"]
320            _tuplefilter = tuplegetter(*_translated_indexes)
321        else:
322            _translated_indexes = _tuplefilter = None
323        self.__init__(  # type: ignore
324            state["_keys"],
325            _translated_indexes=_translated_indexes,
326            _tuplefilter=_tuplefilter,
327        )
328
329    def _index_for_key(self, key: Any, raiseerr: bool = True) -> int:
330        if int in key.__class__.__mro__:
331            key = self._keys[key]
332        try:
333            rec = self._keymap[key]
334        except KeyError as ke:
335            rec = self._key_fallback(key, ke, raiseerr)
336
337        return rec[0]  # type: ignore[no-any-return]
338
339    def _indexes_for_keys(self, keys: Sequence[Any]) -> Sequence[int]:
340        return [self._keymap[key][0] for key in keys]
341
342    def _metadata_for_keys(
343        self, keys: Sequence[Any]
344    ) -> Iterator[_KeyMapRecType]:
345        for key in keys:
346            if int in key.__class__.__mro__:
347                key = self._keys[key]
348
349            try:
350                rec = self._keymap[key]
351            except KeyError as ke:
352                rec = self._key_fallback(key, ke, True)
353
354            yield rec
355
356    def _reduce(self, keys: Sequence[Any]) -> ResultMetaData:
357        try:
358            metadata_for_keys = [
359                self._keymap[
360                    self._keys[key] if int in key.__class__.__mro__ else key
361                ]
362                for key in keys
363            ]
364        except KeyError as ke:
365            self._key_fallback(ke.args[0], ke, True)
366
367        indexes: Sequence[int]
368        new_keys: Sequence[str]
369        extra: Sequence[Any]
370        indexes, new_keys, extra = zip(*metadata_for_keys)
371
372        if self._translated_indexes:
373            indexes = [self._translated_indexes[idx] for idx in indexes]
374
375        tup = tuplegetter(*indexes)
376
377        new_metadata = SimpleResultMetaData(
378            new_keys,
379            extra=extra,
380            _tuplefilter=tup,
381            _translated_indexes=indexes,
382            _processors=self._processors,
383            _unique_filters=self._unique_filters,
384        )
385
386        return new_metadata
387
388
389def result_tuple(
390    fields: Sequence[str], extra: Optional[Any] = None
391) -> Callable[[Iterable[Any]], Row[Any]]:
392    parent = SimpleResultMetaData(fields, extra)
393    return functools.partial(
394        Row, parent, parent._effective_processors, parent._key_to_index
395    )
396
397
398# a symbol that indicates to internal Result methods that
399# "no row is returned".  We can't use None for those cases where a scalar
400# filter is applied to rows.
401class _NoRow(Enum):
402    _NO_ROW = 0
403
404
405_NO_ROW = _NoRow._NO_ROW
406
407
408class ResultInternal(InPlaceGenerative, Generic[_R]):
409    __slots__ = ()
410
411    _real_result: Optional[Result[Any]] = None
412    _generate_rows: bool = True
413    _row_logging_fn: Optional[Callable[[Any], Any]]
414
415    _unique_filter_state: Optional[_UniqueFilterStateType] = None
416    _post_creational_filter: Optional[Callable[[Any], Any]] = None
417    _is_cursor = False
418
419    _metadata: ResultMetaData
420
421    _source_supports_scalars: bool
422
423    def _fetchiter_impl(self) -> Iterator[_InterimRowType[Row[Any]]]:
424        raise NotImplementedError()
425
426    def _fetchone_impl(
427        self, hard_close: bool = False
428    ) -> Optional[_InterimRowType[Row[Any]]]:
429        raise NotImplementedError()
430
431    def _fetchmany_impl(
432        self, size: Optional[int] = None
433    ) -> List[_InterimRowType[Row[Any]]]:
434        raise NotImplementedError()
435
436    def _fetchall_impl(self) -> List[_InterimRowType[Row[Any]]]:
437        raise NotImplementedError()
438
439    def _soft_close(self, hard: bool = False) -> None:
440        raise NotImplementedError()
441
442    @HasMemoized_ro_memoized_attribute
443    def _row_getter(self) -> Optional[Callable[..., _R]]:
444        real_result: Result[Any] = (
445            self._real_result
446            if self._real_result
447            else cast("Result[Any]", self)
448        )
449
450        if real_result._source_supports_scalars:
451            if not self._generate_rows:
452                return None
453            else:
454                _proc = Row
455
456                def process_row(
457                    metadata: ResultMetaData,
458                    processors: Optional[_ProcessorsType],
459                    key_to_index: Mapping[_KeyType, int],
460                    scalar_obj: Any,
461                ) -> Row[Any]:
462                    return _proc(
463                        metadata, processors, key_to_index, (scalar_obj,)
464                    )
465
466        else:
467            process_row = Row  # type: ignore
468
469        metadata = self._metadata
470
471        key_to_index = metadata._key_to_index
472        processors = metadata._effective_processors
473        tf = metadata._tuplefilter
474
475        if tf and not real_result._source_supports_scalars:
476            if processors:
477                processors = tf(processors)
478
479            _make_row_orig: Callable[..., _R] = functools.partial(  # type: ignore  # noqa E501
480                process_row, metadata, processors, key_to_index
481            )
482
483            fixed_tf = tf
484
485            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
486                return _make_row_orig(fixed_tf(row))
487
488        else:
489            make_row = functools.partial(  # type: ignore
490                process_row, metadata, processors, key_to_index
491            )
492
493        if real_result._row_logging_fn:
494            _log_row = real_result._row_logging_fn
495            _make_row = make_row
496
497            def make_row(row: _InterimRowType[Row[Any]]) -> _R:
498                return _log_row(_make_row(row))  # type: ignore
499
500        return make_row
501
502    @HasMemoized_ro_memoized_attribute
503    def _iterator_getter(self) -> Callable[..., Iterator[_R]]:
504        make_row = self._row_getter
505
506        post_creational_filter = self._post_creational_filter
507
508        if self._unique_filter_state:
509            uniques, strategy = self._unique_strategy
510
511            def iterrows(self: Result[Any]) -> Iterator[_R]:
512                for raw_row in self._fetchiter_impl():
513                    obj: _InterimRowType[Any] = (
514                        make_row(raw_row) if make_row else raw_row
515                    )
516                    hashed = strategy(obj) if strategy else obj
517                    if hashed in uniques:
518                        continue
519                    uniques.add(hashed)
520                    if post_creational_filter:
521                        obj = post_creational_filter(obj)
522                    yield obj  # type: ignore
523
524        else:
525
526            def iterrows(self: Result[Any]) -> Iterator[_R]:
527                for raw_row in self._fetchiter_impl():
528                    row: _InterimRowType[Any] = (
529                        make_row(raw_row) if make_row else raw_row
530                    )
531                    if post_creational_filter:
532                        row = post_creational_filter(row)
533                    yield row  # type: ignore
534
535        return iterrows
536
537    def _raw_all_rows(self) -> List[_R]:
538        make_row = self._row_getter
539        assert make_row is not None
540        rows = self._fetchall_impl()
541        return [make_row(row) for row in rows]
542
543    def _allrows(self) -> List[_R]:
544        post_creational_filter = self._post_creational_filter
545
546        make_row = self._row_getter
547
548        rows = self._fetchall_impl()
549        made_rows: List[_InterimRowType[_R]]
550        if make_row:
551            made_rows = [make_row(row) for row in rows]
552        else:
553            made_rows = rows  # type: ignore
554
555        interim_rows: List[_R]
556
557        if self._unique_filter_state:
558            uniques, strategy = self._unique_strategy
559
560            interim_rows = [
561                made_row  # type: ignore
562                for made_row, sig_row in [
563                    (
564                        made_row,
565                        strategy(made_row) if strategy else made_row,
566                    )
567                    for made_row in made_rows
568                ]
569                if sig_row not in uniques and not uniques.add(sig_row)  # type: ignore # noqa: E501
570            ]
571        else:
572            interim_rows = made_rows  # type: ignore
573
574        if post_creational_filter:
575            interim_rows = [
576                post_creational_filter(row) for row in interim_rows
577            ]
578        return interim_rows
579
580    @HasMemoized_ro_memoized_attribute
581    def _onerow_getter(
582        self,
583    ) -> Callable[..., Union[Literal[_NoRow._NO_ROW], _R]]:
584        make_row = self._row_getter
585
586        post_creational_filter = self._post_creational_filter
587
588        if self._unique_filter_state:
589            uniques, strategy = self._unique_strategy
590
591            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
592                _onerow = self._fetchone_impl
593                while True:
594                    row = _onerow()
595                    if row is None:
596                        return _NO_ROW
597                    else:
598                        obj: _InterimRowType[Any] = (
599                            make_row(row) if make_row else row
600                        )
601                        hashed = strategy(obj) if strategy else obj
602                        if hashed in uniques:
603                            continue
604                        else:
605                            uniques.add(hashed)
606                        if post_creational_filter:
607                            obj = post_creational_filter(obj)
608                        return obj  # type: ignore
609
610        else:
611
612            def onerow(self: Result[Any]) -> Union[_NoRow, _R]:
613                row = self._fetchone_impl()
614                if row is None:
615                    return _NO_ROW
616                else:
617                    interim_row: _InterimRowType[Any] = (
618                        make_row(row) if make_row else row
619                    )
620                    if post_creational_filter:
621                        interim_row = post_creational_filter(interim_row)
622                    return interim_row  # type: ignore
623
624        return onerow
625
626    @HasMemoized_ro_memoized_attribute
627    def _manyrow_getter(self) -> Callable[..., List[_R]]:
628        make_row = self._row_getter
629
630        post_creational_filter = self._post_creational_filter
631
632        if self._unique_filter_state:
633            uniques, strategy = self._unique_strategy
634
635            def filterrows(
636                make_row: Optional[Callable[..., _R]],
637                rows: List[Any],
638                strategy: Optional[Callable[[List[Any]], Any]],
639                uniques: Set[Any],
640            ) -> List[_R]:
641                if make_row:
642                    rows = [make_row(row) for row in rows]
643
644                if strategy:
645                    made_rows = (
646                        (made_row, strategy(made_row)) for made_row in rows
647                    )
648                else:
649                    made_rows = ((made_row, made_row) for made_row in rows)
650                return [
651                    made_row
652                    for made_row, sig_row in made_rows
653                    if sig_row not in uniques and not uniques.add(sig_row)  # type: ignore  # noqa: E501
654                ]
655
656            def manyrows(
657                self: ResultInternal[_R], num: Optional[int]
658            ) -> List[_R]:
659                collect: List[_R] = []
660
661                _manyrows = self._fetchmany_impl
662
663                if num is None:
664                    # if None is passed, we don't know the default
665                    # manyrows number, DBAPI has this as cursor.arraysize
666                    # different DBAPIs / fetch strategies may be different.
667                    # do a fetch to find what the number is.  if there are
668                    # only fewer rows left, then it doesn't matter.
669                    real_result = (
670                        self._real_result
671                        if self._real_result
672                        else cast("Result[Any]", self)
673                    )
674                    if real_result._yield_per:
675                        num_required = num = real_result._yield_per
676                    else:
677                        rows = _manyrows(num)
678                        num = len(rows)
679                        assert make_row is not None
680                        collect.extend(
681                            filterrows(make_row, rows, strategy, uniques)
682                        )
683                        num_required = num - len(collect)
684                else:
685                    num_required = num
686
687                assert num is not None
688
689                while num_required:
690                    rows = _manyrows(num_required)
691                    if not rows:
692                        break
693
694                    collect.extend(
695                        filterrows(make_row, rows, strategy, uniques)
696                    )
697                    num_required = num - len(collect)
698
699                if post_creational_filter:
700                    collect = [post_creational_filter(row) for row in collect]
701                return collect
702
703        else:
704
705            def manyrows(
706                self: ResultInternal[_R], num: Optional[int]
707            ) -> List[_R]:
708                if num is None:
709                    real_result = (
710                        self._real_result
711                        if self._real_result
712                        else cast("Result[Any]", self)
713                    )
714                    num = real_result._yield_per
715
716                rows: List[_InterimRowType[Any]] = self._fetchmany_impl(num)
717                if make_row:
718                    rows = [make_row(row) for row in rows]
719                if post_creational_filter:
720                    rows = [post_creational_filter(row) for row in rows]
721                return rows  # type: ignore
722
723        return manyrows
724
725    @overload
726    def _only_one_row(
727        self,
728        raise_for_second_row: bool,
729        raise_for_none: Literal[True],
730        scalar: bool,
731    ) -> _R: ...
732
733    @overload
734    def _only_one_row(
735        self,
736        raise_for_second_row: bool,
737        raise_for_none: bool,
738        scalar: bool,
739    ) -> Optional[_R]: ...
740
741    def _only_one_row(
742        self,
743        raise_for_second_row: bool,
744        raise_for_none: bool,
745        scalar: bool,
746    ) -> Optional[_R]:
747        onerow = self._fetchone_impl
748
749        row: Optional[_InterimRowType[Any]] = onerow(hard_close=True)
750        if row is None:
751            if raise_for_none:
752                raise exc.NoResultFound(
753                    "No row was found when one was required"
754                )
755            else:
756                return None
757
758        if scalar and self._source_supports_scalars:
759            self._generate_rows = False
760            make_row = None
761        else:
762            make_row = self._row_getter
763
764        try:
765            row = make_row(row) if make_row else row
766        except:
767            self._soft_close(hard=True)
768            raise
769
770        if raise_for_second_row:
771            if self._unique_filter_state:
772                # for no second row but uniqueness, need to essentially
773                # consume the entire result :(
774                uniques, strategy = self._unique_strategy
775
776                existing_row_hash = strategy(row) if strategy else row
777
778                while True:
779                    next_row: Any = onerow(hard_close=True)
780                    if next_row is None:
781                        next_row = _NO_ROW
782                        break
783
784                    try:
785                        next_row = make_row(next_row) if make_row else next_row
786
787                        if strategy:
788                            assert next_row is not _NO_ROW
789                            if existing_row_hash == strategy(next_row):
790                                continue
791                        elif row == next_row:
792                            continue
793                        # here, we have a row and it's different
794                        break
795                    except:
796                        self._soft_close(hard=True)
797                        raise
798            else:
799                next_row = onerow(hard_close=True)
800                if next_row is None:
801                    next_row = _NO_ROW
802
803            if next_row is not _NO_ROW:
804                self._soft_close(hard=True)
805                raise exc.MultipleResultsFound(
806                    "Multiple rows were found when exactly one was required"
807                    if raise_for_none
808                    else "Multiple rows were found when one or none "
809                    "was required"
810                )
811        else:
812            next_row = _NO_ROW
813            # if we checked for second row then that would have
814            # closed us :)
815            self._soft_close(hard=True)
816
817        if not scalar:
818            post_creational_filter = self._post_creational_filter
819            if post_creational_filter:
820                row = post_creational_filter(row)
821
822        if scalar and make_row:
823            return row[0]  # type: ignore
824        else:
825            return row  # type: ignore
826
827    def _iter_impl(self) -> Iterator[_R]:
828        return self._iterator_getter(self)
829
830    def _next_impl(self) -> _R:
831        row = self._onerow_getter(self)
832        if row is _NO_ROW:
833            raise StopIteration()
834        else:
835            return row
836
837    @_generative
838    def _column_slices(self, indexes: Sequence[_KeyIndexType]) -> Self:
839        real_result = (
840            self._real_result
841            if self._real_result
842            else cast("Result[Any]", self)
843        )
844
845        if not real_result._source_supports_scalars or len(indexes) != 1:
846            self._metadata = self._metadata._reduce(indexes)
847
848        assert self._generate_rows
849
850        return self
851
852    @HasMemoized.memoized_attribute
853    def _unique_strategy(self) -> _UniqueFilterStateType:
854        assert self._unique_filter_state is not None
855        uniques, strategy = self._unique_filter_state
856
857        real_result = (
858            self._real_result
859            if self._real_result is not None
860            else cast("Result[Any]", self)
861        )
862
863        if not strategy and self._metadata._unique_filters:
864            if (
865                real_result._source_supports_scalars
866                and not self._generate_rows
867            ):
868                strategy = self._metadata._unique_filters[0]
869            else:
870                filters = self._metadata._unique_filters
871                if self._metadata._tuplefilter:
872                    filters = self._metadata._tuplefilter(filters)
873
874                strategy = operator.methodcaller("_filter_on_values", filters)
875        return uniques, strategy
876
877
878class _WithKeys:
879    __slots__ = ()
880
881    _metadata: ResultMetaData
882
883    # used mainly to share documentation on the keys method.
884    def keys(self) -> RMKeyView:
885        """Return an iterable view which yields the string keys that would
886        be represented by each :class:`_engine.Row`.
887
888        The keys can represent the labels of the columns returned by a core
889        statement or the names of the orm classes returned by an orm
890        execution.
891
892        The view also can be tested for key containment using the Python
893        ``in`` operator, which will test both for the string keys represented
894        in the view, as well as for alternate keys such as column objects.
895
896        .. versionchanged:: 1.4 a key view object is returned rather than a
897           plain list.
898
899
900        """
901        return self._metadata.keys
902
903
904class Result(_WithKeys, ResultInternal[Row[_TP]]):
905    """Represent a set of database results.
906
907    .. versionadded:: 1.4  The :class:`_engine.Result` object provides a
908       completely updated usage model and calling facade for SQLAlchemy
909       Core and SQLAlchemy ORM.   In Core, it forms the basis of the
910       :class:`_engine.CursorResult` object which replaces the previous
911       :class:`_engine.ResultProxy` interface.   When using the ORM, a
912       higher level object called :class:`_engine.ChunkedIteratorResult`
913       is normally used.
914
915    .. note:: In SQLAlchemy 1.4 and above, this object is
916       used for ORM results returned by :meth:`_orm.Session.execute`, which can
917       yield instances of ORM mapped objects either individually or within
918       tuple-like rows. Note that the :class:`_engine.Result` object does not
919       deduplicate instances or rows automatically as is the case with the
920       legacy :class:`_orm.Query` object. For in-Python de-duplication of
921       instances or rows, use the :meth:`_engine.Result.unique` modifier
922       method.
923
924    .. seealso::
925
926        :ref:`tutorial_fetching_rows` - in the :doc:`/tutorial/index`
927
928    """
929
930    __slots__ = ("_metadata", "__dict__")
931
932    _row_logging_fn: Optional[Callable[[Row[Any]], Row[Any]]] = None
933
934    _source_supports_scalars: bool = False
935
936    _yield_per: Optional[int] = None
937
938    _attributes: util.immutabledict[Any, Any] = util.immutabledict()
939
940    def __init__(self, cursor_metadata: ResultMetaData):
941        self._metadata = cursor_metadata
942
943    def __enter__(self) -> Self:
944        return self
945
946    def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
947        self.close()
948
949    def close(self) -> None:
950        """close this :class:`_engine.Result`.
951
952        The behavior of this method is implementation specific, and is
953        not implemented by default.    The method should generally end
954        the resources in use by the result object and also cause any
955        subsequent iteration or row fetching to raise
956        :class:`.ResourceClosedError`.
957
958        .. versionadded:: 1.4.27 - ``.close()`` was previously not generally
959           available for all :class:`_engine.Result` classes, instead only
960           being available on the :class:`_engine.CursorResult` returned for
961           Core statement executions. As most other result objects, namely the
962           ones used by the ORM, are proxying a :class:`_engine.CursorResult`
963           in any case, this allows the underlying cursor result to be closed
964           from the outside facade for the case when the ORM query is using
965           the ``yield_per`` execution option where it does not immediately
966           exhaust and autoclose the database cursor.
967
968        """
969        self._soft_close(hard=True)
970
971    @property
972    def _soft_closed(self) -> bool:
973        raise NotImplementedError()
974
975    @property
976    def closed(self) -> bool:
977        """return ``True`` if this :class:`_engine.Result` reports .closed
978
979        .. versionadded:: 1.4.43
980
981        """
982        raise NotImplementedError()
983
984    @_generative
985    def yield_per(self, num: int) -> Self:
986        """Configure the row-fetching strategy to fetch ``num`` rows at a time.
987
988        This impacts the underlying behavior of the result when iterating over
989        the result object, or otherwise making use of  methods such as
990        :meth:`_engine.Result.fetchone` that return one row at a time.   Data
991        from the underlying cursor or other data source will be buffered up to
992        this many rows in memory, and the buffered collection will then be
993        yielded out one row at a time or as many rows are requested. Each time
994        the buffer clears, it will be refreshed to this many rows or as many
995        rows remain if fewer remain.
996
997        The :meth:`_engine.Result.yield_per` method is generally used in
998        conjunction with the
999        :paramref:`_engine.Connection.execution_options.stream_results`
1000        execution option, which will allow the database dialect in use to make
1001        use of a server side cursor, if the DBAPI supports a specific "server
1002        side cursor" mode separate from its default mode of operation.
1003
1004        .. tip::
1005
1006            Consider using the
1007            :paramref:`_engine.Connection.execution_options.yield_per`
1008            execution option, which will simultaneously set
1009            :paramref:`_engine.Connection.execution_options.stream_results`
1010            to ensure the use of server side cursors, as well as automatically
1011            invoke the :meth:`_engine.Result.yield_per` method to establish
1012            a fixed row buffer size at once.
1013
1014            The :paramref:`_engine.Connection.execution_options.yield_per`
1015            execution option is available for ORM operations, with
1016            :class:`_orm.Session`-oriented use described at
1017            :ref:`orm_queryguide_yield_per`. The Core-only version which works
1018            with :class:`_engine.Connection` is new as of SQLAlchemy 1.4.40.
1019
1020        .. versionadded:: 1.4
1021
1022        :param num: number of rows to fetch each time the buffer is refilled.
1023         If set to a value below 1, fetches all rows for the next buffer.
1024
1025        .. seealso::
1026
1027            :ref:`engine_stream_results` - describes Core behavior for
1028            :meth:`_engine.Result.yield_per`
1029
1030            :ref:`orm_queryguide_yield_per` - in the :ref:`queryguide_toplevel`
1031
1032        """
1033        self._yield_per = num
1034        return self
1035
1036    @_generative
1037    def unique(self, strategy: Optional[_UniqueFilterType] = None) -> Self:
1038        """Apply unique filtering to the objects returned by this
1039        :class:`_engine.Result`.
1040
1041        When this filter is applied with no arguments, the rows or objects
1042        returned will filtered such that each row is returned uniquely. The
1043        algorithm used to determine this uniqueness is by default the Python
1044        hashing identity of the whole tuple.   In some cases a specialized
1045        per-entity hashing scheme may be used, such as when using the ORM, a
1046        scheme is applied which  works against the primary key identity of
1047        returned objects.
1048
1049        The unique filter is applied **after all other filters**, which means
1050        if the columns returned have been refined using a method such as the
1051        :meth:`_engine.Result.columns` or :meth:`_engine.Result.scalars`
1052        method, the uniquing is applied to **only the column or columns
1053        returned**.   This occurs regardless of the order in which these
1054        methods have been called upon the :class:`_engine.Result` object.
1055
1056        The unique filter also changes the calculus used for methods like
1057        :meth:`_engine.Result.fetchmany` and :meth:`_engine.Result.partitions`.
1058        When using :meth:`_engine.Result.unique`, these methods will continue
1059        to yield the number of rows or objects requested, after uniquing
1060        has been applied.  However, this necessarily impacts the buffering
1061        behavior of the underlying cursor or datasource, such that multiple
1062        underlying calls to ``cursor.fetchmany()`` may be necessary in order
1063        to accumulate enough objects in order to provide a unique collection
1064        of the requested size.
1065
1066        :param strategy: a callable that will be applied to rows or objects
1067         being iterated, which should return an object that represents the
1068         unique value of the row.   A Python ``set()`` is used to store
1069         these identities.   If not passed, a default uniqueness strategy
1070         is used which may have been assembled by the source of this
1071         :class:`_engine.Result` object.
1072
1073        """
1074        self._unique_filter_state = (set(), strategy)
1075        return self
1076
1077    def columns(self, *col_expressions: _KeyIndexType) -> Self:
1078        r"""Establish the columns that should be returned in each row.
1079
1080        This method may be used to limit the columns returned as well
1081        as to reorder them.   The given list of expressions are normally
1082        a series of integers or string key names.   They may also be
1083        appropriate :class:`.ColumnElement` objects which correspond to
1084        a given statement construct.
1085
1086        .. versionchanged:: 2.0  Due to a bug in 1.4, the
1087           :meth:`_engine.Result.columns` method had an incorrect behavior
1088           where calling upon the method with just one index would cause the
1089           :class:`_engine.Result` object to yield scalar values rather than
1090           :class:`_engine.Row` objects.   In version 2.0, this behavior
1091           has been corrected such that calling upon
1092           :meth:`_engine.Result.columns` with a single index will
1093           produce a :class:`_engine.Result` object that continues
1094           to yield :class:`_engine.Row` objects, which include
1095           only a single column.
1096
1097        E.g.::
1098
1099            statement = select(table.c.x, table.c.y, table.c.z)
1100            result = connection.execute(statement)
1101
1102            for z, y in result.columns('z', 'y'):
1103                # ...
1104
1105
1106        Example of using the column objects from the statement itself::
1107
1108            for z, y in result.columns(
1109                    statement.selected_columns.c.z,
1110                    statement.selected_columns.c.y
1111            ):
1112                # ...
1113
1114        .. versionadded:: 1.4
1115
1116        :param \*col_expressions: indicates columns to be returned.  Elements
1117         may be integer row indexes, string column names, or appropriate
1118         :class:`.ColumnElement` objects corresponding to a select construct.
1119
1120        :return: this :class:`_engine.Result` object with the modifications
1121         given.
1122
1123        """
1124        return self._column_slices(col_expressions)
1125
1126    @overload
1127    def scalars(self: Result[Tuple[_T]]) -> ScalarResult[_T]: ...
1128
1129    @overload
1130    def scalars(
1131        self: Result[Tuple[_T]], index: Literal[0]
1132    ) -> ScalarResult[_T]: ...
1133
1134    @overload
1135    def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]: ...
1136
1137    def scalars(self, index: _KeyIndexType = 0) -> ScalarResult[Any]:
1138        """Return a :class:`_engine.ScalarResult` filtering object which
1139        will return single elements rather than :class:`_row.Row` objects.
1140
1141        E.g.::
1142
1143            >>> result = conn.execute(text("select int_id from table"))
1144            >>> result.scalars().all()
1145            [1, 2, 3]
1146
1147        When results are fetched from the :class:`_engine.ScalarResult`
1148        filtering object, the single column-row that would be returned by the
1149        :class:`_engine.Result` is instead returned as the column's value.
1150
1151        .. versionadded:: 1.4
1152
1153        :param index: integer or row key indicating the column to be fetched
1154         from each row, defaults to ``0`` indicating the first column.
1155
1156        :return: a new :class:`_engine.ScalarResult` filtering object referring
1157         to this :class:`_engine.Result` object.
1158
1159        """
1160        return ScalarResult(self, index)
1161
1162    def _getter(
1163        self, key: _KeyIndexType, raiseerr: bool = True
1164    ) -> Optional[Callable[[Row[Any]], Any]]:
1165        """return a callable that will retrieve the given key from a
1166        :class:`_engine.Row`.
1167
1168        """
1169        if self._source_supports_scalars:
1170            raise NotImplementedError(
1171                "can't use this function in 'only scalars' mode"
1172            )
1173        return self._metadata._getter(key, raiseerr)
1174
1175    def _tuple_getter(self, keys: Sequence[_KeyIndexType]) -> _TupleGetterType:
1176        """return a callable that will retrieve the given keys from a
1177        :class:`_engine.Row`.
1178
1179        """
1180        if self._source_supports_scalars:
1181            raise NotImplementedError(
1182                "can't use this function in 'only scalars' mode"
1183            )
1184        return self._metadata._row_as_tuple_getter(keys)
1185
1186    def mappings(self) -> MappingResult:
1187        """Apply a mappings filter to returned rows, returning an instance of
1188        :class:`_engine.MappingResult`.
1189
1190        When this filter is applied, fetching rows will return
1191        :class:`_engine.RowMapping` objects instead of :class:`_engine.Row`
1192        objects.
1193
1194        .. versionadded:: 1.4
1195
1196        :return: a new :class:`_engine.MappingResult` filtering object
1197         referring to this :class:`_engine.Result` object.
1198
1199        """
1200

Showing the first 1,200 of 2383 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai