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