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