1# engine/row.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 row constructs including :class:`.Row`."""
9
10from __future__ import annotations
11
12from abc import ABC
13import collections.abc as collections_abc
14import operator
15import typing
16from typing import Any
17from typing import Callable
18from typing import Dict
19from typing import Generic
20from typing import Iterator
21from typing import Mapping
22from typing import Optional
23from typing import Sequence
24from typing import Tuple
25from typing import TYPE_CHECKING
26
27from ._row_cy import BaseRow as BaseRow
28from ..sql import util as sql_util
29from ..util import deprecated
30from ..util.typing import TypeVarTuple
31from ..util.typing import Unpack
32
33if TYPE_CHECKING:
34 from typing import Tuple as _RowBase
35
36 from .result import _KeyType
37 from .result import _ProcessorsType
38 from .result import RMKeyView
39else:
40 _RowBase = Sequence
41
42
43_Ts = TypeVarTuple("_Ts")
44
45
46class Row(BaseRow, _RowBase[Unpack[_Ts]], Generic[Unpack[_Ts]]):
47 """Represent a single result row.
48
49 The :class:`.Row` object represents a row of a database result. It is
50 typically associated in the 1.x series of SQLAlchemy with the
51 :class:`_engine.CursorResult` object, however is also used by the ORM for
52 tuple-like results as of SQLAlchemy 1.4.
53
54 The :class:`.Row` object seeks to act as much like a Python named
55 tuple as possible. For mapping (i.e. dictionary) behavior on a row,
56 such as testing for containment of keys, refer to the :attr:`.Row._mapping`
57 attribute.
58
59 .. seealso::
60
61 :ref:`tutorial_selecting_data` - includes examples of selecting
62 rows from SELECT statements.
63
64 .. versionchanged:: 1.4
65
66 Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a
67 "proxy" object in that it contains the final form of data within it,
68 and now acts mostly like a named tuple. Mapping-like functionality is
69 moved to the :attr:`.Row._mapping` attribute. See
70 :ref:`change_4710_core` for background on this change.
71
72 """
73
74 __slots__ = ()
75
76 @deprecated(
77 "2.1.0",
78 "The :meth:`.Row._tuple` method is deprecated, :class:`.Row` "
79 "now behaves like a tuple and can unpack types directly.",
80 )
81 def _tuple(self) -> Tuple[Unpack[_Ts]]:
82 """Return a 'tuple' form of this :class:`.Row`.
83
84 At runtime, this method returns "self"; the :class:`.Row` object is
85 already a named tuple. However, at the typing level, if this
86 :class:`.Row` is typed, the "tuple" return type will be a :pep:`484`
87 ``Tuple`` datatype that contains typing information about individual
88 elements, supporting typed unpacking and attribute access.
89
90 .. versionadded:: 2.0.19 - The :meth:`.Row._tuple` method supersedes
91 the previous :meth:`.Row.tuple` method, which is now underscored
92 to avoid name conflicts with column names in the same way as other
93 named-tuple methods on :class:`.Row`.
94
95 .. seealso::
96
97 :ref:`change_10635` - describes a migration path from this
98 workaround for SQLAlchemy 2.1.
99
100 :attr:`.Row._t` - shorthand attribute notation
101
102 :meth:`.Result.tuples`
103
104
105 """
106 return self
107
108 @deprecated(
109 "2.0.19",
110 "The :meth:`.Row.tuple` method is deprecated in favor of "
111 ":meth:`.Row._tuple`; all :class:`.Row` "
112 "methods and library-level attributes are intended to be underscored "
113 "to avoid name conflicts. Please use :meth:`Row._tuple`.",
114 )
115 def tuple(self) -> Tuple[Unpack[_Ts]]:
116 """Return a 'tuple' form of this :class:`.Row`.
117
118 .. versionadded:: 2.0
119
120 .. seealso::
121
122 :ref:`change_10635` - describes a migration path from this
123 workaround for SQLAlchemy 2.1.
124
125 """
126 return self._tuple()
127
128 @property
129 @deprecated(
130 "2.1.0",
131 "The :attr:`.Row._t` attribute is deprecated, :class:`.Row` "
132 "now behaves like a tuple and can unpack types directly.",
133 )
134 def _t(self) -> Tuple[Unpack[_Ts]]:
135 """A synonym for :meth:`.Row._tuple`.
136
137 .. versionadded:: 2.0.19 - The :attr:`.Row._t` attribute supersedes
138 the previous :attr:`.Row.t` attribute, which is now underscored
139 to avoid name conflicts with column names in the same way as other
140 named-tuple methods on :class:`.Row`.
141
142 .. seealso::
143
144 :ref:`change_10635` - describes a migration path from this
145 workaround for SQLAlchemy 2.1.
146
147 :attr:`.Result.t`
148 """
149 return self
150
151 @property
152 @deprecated(
153 "2.0.19",
154 "The :attr:`.Row.t` attribute is deprecated in favor of "
155 ":attr:`.Row._t`; all :class:`.Row` "
156 "methods and library-level attributes are intended to be underscored "
157 "to avoid name conflicts. Please use :attr:`Row._t`.",
158 )
159 def t(self) -> Tuple[Unpack[_Ts]]:
160 """A synonym for :meth:`.Row._tuple`.
161
162 .. versionadded:: 2.0
163
164 .. seealso::
165
166 :ref:`change_10635` - describes a migration path from this
167 workaround for SQLAlchemy 2.1.
168
169 """
170 return self._t
171
172 @property
173 def _mapping(self) -> RowMapping:
174 """Return a :class:`.RowMapping` for this :class:`.Row`.
175
176 This object provides a consistent Python mapping (i.e. dictionary)
177 interface for the data contained within the row. The :class:`.Row`
178 by itself behaves like a named tuple.
179
180 .. seealso::
181
182 :attr:`.Row._fields`
183
184 .. versionadded:: 1.4
185
186 """
187 return RowMapping(self._parent, None, self._key_to_index, self._data)
188
189 def _filter_on_values(
190 self, processor: Optional[_ProcessorsType]
191 ) -> Row[Unpack[_Ts]]:
192 return Row(self._parent, processor, self._key_to_index, self._data)
193
194 if not TYPE_CHECKING:
195
196 def _special_name_accessor(name: str) -> Any:
197 """Handle ambiguous names such as "count" and "index" """
198
199 @property
200 def go(self: Row) -> Any:
201 if self._parent._has_key(name):
202 return self.__getattr__(name)
203 else:
204
205 def meth(*arg: Any, **kw: Any) -> Any:
206 return getattr(collections_abc.Sequence, name)(
207 self, *arg, **kw
208 )
209
210 return meth
211
212 return go
213
214 count = _special_name_accessor("count")
215 index = _special_name_accessor("index")
216
217 def _op(self, other: Any, op: Callable[[Any, Any], bool]) -> bool:
218 return (
219 op(self._to_tuple_instance(), other._to_tuple_instance())
220 if isinstance(other, Row)
221 else op(self._to_tuple_instance(), other)
222 )
223
224 __hash__ = BaseRow.__hash__
225
226 def __lt__(self, other: Any) -> bool:
227 return self._op(other, operator.lt)
228
229 def __le__(self, other: Any) -> bool:
230 return self._op(other, operator.le)
231
232 def __ge__(self, other: Any) -> bool:
233 return self._op(other, operator.ge)
234
235 def __gt__(self, other: Any) -> bool:
236 return self._op(other, operator.gt)
237
238 def __eq__(self, other: Any) -> bool:
239 return self._op(other, operator.eq)
240
241 def __ne__(self, other: Any) -> bool:
242 return self._op(other, operator.ne)
243
244 def __repr__(self) -> str:
245 return repr(sql_util._repr_row(self))
246
247 @property
248 def _fields(self) -> Tuple[str, ...]:
249 """Return a tuple of string keys as represented by this
250 :class:`.Row`.
251
252 The keys can represent the labels of the columns returned by a core
253 statement or the names of the orm classes returned by an orm
254 execution.
255
256 This attribute is analogous to the Python named tuple ``._fields``
257 attribute.
258
259 .. versionadded:: 1.4
260
261 .. seealso::
262
263 :attr:`.Row._mapping`
264
265 """
266 return tuple([k for k in self._parent.keys if k is not None])
267
268 def _asdict(self) -> Dict[str, Any]:
269 """Return a new dict which maps field names to their corresponding
270 values.
271
272 This method is analogous to the Python named tuple ``._asdict()``
273 method, and works by applying the ``dict()`` constructor to the
274 :attr:`.Row._mapping` attribute.
275
276 .. versionadded:: 1.4
277
278 .. seealso::
279
280 :attr:`.Row._mapping`
281
282 """
283 return dict(self._mapping)
284
285
286BaseRowProxy = BaseRow
287RowProxy = Row
288
289
290class ROMappingView(ABC):
291 __slots__ = ()
292
293 _items: Sequence[Any]
294 _mapping: Mapping["_KeyType", Any]
295
296 def __init__(
297 self, mapping: Mapping["_KeyType", Any], items: Sequence[Any]
298 ):
299 self._mapping = mapping # type: ignore[misc]
300 self._items = items # type: ignore[misc]
301
302 def __len__(self) -> int:
303 return len(self._items)
304
305 def __repr__(self) -> str:
306 return "{0.__class__.__name__}({0._mapping!r})".format(self)
307
308 def __iter__(self) -> Iterator[Any]:
309 return iter(self._items)
310
311 def __contains__(self, item: Any) -> bool:
312 return item in self._items
313
314 def __eq__(self, other: Any) -> bool:
315 return list(other) == list(self)
316
317 def __ne__(self, other: Any) -> bool:
318 return list(other) != list(self)
319
320
321class ROMappingKeysValuesView(
322 ROMappingView, typing.KeysView["_KeyType"], typing.ValuesView[Any]
323):
324 __slots__ = ("_items",) # mapping slot is provided by KeysView
325
326
327class ROMappingItemsView(ROMappingView, typing.ItemsView["_KeyType", Any]):
328 __slots__ = ("_items",) # mapping slot is provided by ItemsView
329
330
331class RowMapping(BaseRow, typing.Mapping["_KeyType", Any]):
332 """A ``Mapping`` that maps column names and objects to :class:`.Row`
333 values.
334
335 The :class:`.RowMapping` is available from a :class:`.Row` via the
336 :attr:`.Row._mapping` attribute, as well as from the iterable interface
337 provided by the :class:`.MappingResult` object returned by the
338 :meth:`_engine.Result.mappings` method.
339
340 :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
341 the contents of the row. This includes support for testing of
342 containment of specific keys (string column names or objects), as well
343 as iteration of keys, values, and items::
344
345 for row in result:
346 if "a" in row._mapping:
347 print("Column 'a': %s" % row._mapping["a"])
348
349 print("Column b: %s" % row._mapping[table.c.b])
350
351 .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the
352 mapping-like access previously provided by a database result row,
353 which now seeks to behave mostly like a named tuple.
354
355 """
356
357 __slots__ = ()
358
359 if TYPE_CHECKING:
360
361 def __getitem__(self, key: _KeyType) -> Any: ...
362
363 else:
364 __getitem__ = BaseRow._get_by_key_impl_mapping
365
366 def __iter__(self) -> Iterator[str]:
367 return (k for k in self._parent.keys if k is not None)
368
369 def __contains__(self, key: object) -> bool:
370 return self._parent._has_key(key)
371
372 def __repr__(self) -> str:
373 return repr(dict(self))
374
375 def items(self) -> ROMappingItemsView:
376 """Return a view of key/value tuples for the elements in the
377 underlying :class:`.Row`.
378
379 """
380 return ROMappingItemsView(
381 self, [(key, self[key]) for key in self.keys()]
382 )
383
384 def keys(self) -> RMKeyView:
385 """Return a view of 'keys' for string column names represented
386 by the underlying :class:`.Row`.
387
388 """
389
390 return self._parent.keys
391
392 def values(self) -> ROMappingKeysValuesView:
393 """Return a view of values for the values represented in the
394 underlying :class:`.Row`.
395
396 """
397 return ROMappingKeysValuesView(self, self._values_impl())