Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/row.py: 41%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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
8"""Define row constructs including :class:`.Row`."""
11import operator
13from .. import util
14from ..sql import util as sql_util
15from ..util.compat import collections_abc
17MD_INDEX = 0 # integer index in cursor.description
19# This reconstructor is necessary so that pickles with the C extension or
20# without use the same Binary format.
21try:
22 # We need a different reconstructor on the C extension so that we can
23 # add extra checks that fields have correctly been initialized by
24 # __setstate__.
25 from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor
27 # The extra function embedding is needed so that the
28 # reconstructor function has the same signature whether or not
29 # the extension is present.
30 def rowproxy_reconstructor(cls, state):
31 return safe_rowproxy_reconstructor(cls, state)
34except ImportError:
36 def rowproxy_reconstructor(cls, state):
37 obj = cls.__new__(cls)
38 obj.__setstate__(state)
39 return obj
42KEY_INTEGER_ONLY = 0
43"""__getitem__ only allows integer values, raises TypeError otherwise"""
45KEY_OBJECTS_ONLY = 1
46"""__getitem__ only allows string/object values, raises TypeError otherwise"""
48KEY_OBJECTS_BUT_WARN = 2
49"""__getitem__ allows integer or string/object values, but emits a 2.0
50deprecation warning if string/object is passed"""
52KEY_OBJECTS_NO_WARN = 3
53"""__getitem__ allows integer or string/object values with no warnings
54or errors."""
56try:
57 from sqlalchemy.cresultproxy import BaseRow
59 _baserow_usecext = True
60except ImportError:
61 _baserow_usecext = False
63 class BaseRow(object):
64 __slots__ = ("_parent", "_data", "_keymap", "_key_style")
66 def __init__(self, parent, processors, keymap, key_style, data):
67 """Row objects are constructed by CursorResult objects."""
69 object.__setattr__(self, "_parent", parent)
71 if processors:
72 object.__setattr__(
73 self,
74 "_data",
75 tuple(
76 [
77 proc(value) if proc else value
78 for proc, value in zip(processors, data)
79 ]
80 ),
81 )
82 else:
83 object.__setattr__(self, "_data", tuple(data))
85 object.__setattr__(self, "_keymap", keymap)
87 object.__setattr__(self, "_key_style", key_style)
89 def __reduce__(self):
90 return (
91 rowproxy_reconstructor,
92 (self.__class__, self.__getstate__()),
93 )
95 def _filter_on_values(self, filters):
96 return Row(
97 self._parent,
98 filters,
99 self._keymap,
100 self._key_style,
101 self._data,
102 )
104 def _values_impl(self):
105 return list(self)
107 def __iter__(self):
108 return iter(self._data)
110 def __len__(self):
111 return len(self._data)
113 def __hash__(self):
114 return hash(self._data)
116 def _get_by_int_impl(self, key):
117 return self._data[key]
119 def _get_by_key_impl(self, key):
120 if int in key.__class__.__mro__:
121 return self._data[key]
123 if self._key_style == KEY_INTEGER_ONLY:
124 self._parent._raise_for_nonint(key)
126 # the following is all LegacyRow support. none of this
127 # should be called if not LegacyRow
128 # assert isinstance(self, LegacyRow)
130 try:
131 rec = self._keymap[key]
132 except KeyError as ke:
133 if isinstance(key, slice):
134 return tuple(self._data[key])
135 else:
136 rec = self._parent._key_fallback(key, ke)
137 except TypeError:
138 if isinstance(key, slice):
139 return tuple(self._data[key])
140 else:
141 raise
143 mdindex = rec[MD_INDEX]
144 if mdindex is None:
145 self._parent._raise_for_ambiguous_column_name(rec)
147 elif self._key_style == KEY_OBJECTS_BUT_WARN and mdindex != key:
148 self._parent._warn_for_nonint(key)
150 return self._data[mdindex]
152 # The original 1.4 plan was that Row would not allow row["str"]
153 # access, however as the C extensions were inadvertently allowing
154 # this coupled with the fact that orm Session sets future=True,
155 # this allows a softer upgrade path. see #6218
156 __getitem__ = _get_by_key_impl
158 def _get_by_key_impl_mapping(self, key):
159 try:
160 rec = self._keymap[key]
161 except KeyError as ke:
162 rec = self._parent._key_fallback(key, ke)
164 mdindex = rec[MD_INDEX]
165 if mdindex is None:
166 self._parent._raise_for_ambiguous_column_name(rec)
167 elif (
168 self._key_style == KEY_OBJECTS_ONLY
169 and int in key.__class__.__mro__
170 ):
171 raise KeyError(key)
173 return self._data[mdindex]
175 def __getattr__(self, name):
176 try:
177 return self._get_by_key_impl_mapping(name)
178 except KeyError as e:
179 util.raise_(AttributeError(e.args[0]), replace_context=e)
182class Row(BaseRow, collections_abc.Sequence):
183 """Represent a single result row.
185 The :class:`.Row` object represents a row of a database result. It is
186 typically associated in the 1.x series of SQLAlchemy with the
187 :class:`_engine.CursorResult` object, however is also used by the ORM for
188 tuple-like results as of SQLAlchemy 1.4.
190 The :class:`.Row` object seeks to act as much like a Python named
191 tuple as possible. For mapping (i.e. dictionary) behavior on a row,
192 such as testing for containment of keys, refer to the :attr:`.Row._mapping`
193 attribute.
195 .. seealso::
197 :ref:`tutorial_selecting_data` - includes examples of selecting
198 rows from SELECT statements.
200 :class:`.LegacyRow` - Compatibility interface introduced in SQLAlchemy
201 1.4.
203 .. versionchanged:: 1.4
205 Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a
206 "proxy" object in that it contains the final form of data within it,
207 and now acts mostly like a named tuple. Mapping-like functionality is
208 moved to the :attr:`.Row._mapping` attribute, but will remain available
209 in SQLAlchemy 1.x series via the :class:`.LegacyRow` class that is used
210 by :class:`_engine.LegacyCursorResult`.
211 See :ref:`change_4710_core` for background
212 on this change.
214 """
216 __slots__ = ()
218 # in 2.0, this should be KEY_INTEGER_ONLY
219 _default_key_style = KEY_OBJECTS_BUT_WARN
221 def __setattr__(self, name, value):
222 raise AttributeError("can't set attribute")
224 def __delattr__(self, name):
225 raise AttributeError("can't delete attribute")
227 @property
228 def _mapping(self):
229 """Return a :class:`.RowMapping` for this :class:`.Row`.
231 This object provides a consistent Python mapping (i.e. dictionary)
232 interface for the data contained within the row. The :class:`.Row`
233 by itself behaves like a named tuple, however in the 1.4 series of
234 SQLAlchemy, the :class:`.LegacyRow` class is still used by Core which
235 continues to have mapping-like behaviors against the row object
236 itself.
238 .. seealso::
240 :attr:`.Row._fields`
242 .. versionadded:: 1.4
244 """
245 return RowMapping(
246 self._parent,
247 None,
248 self._keymap,
249 RowMapping._default_key_style,
250 self._data,
251 )
253 def _special_name_accessor(name):
254 """Handle ambiguous names such as "count" and "index" """
256 @property
257 def go(self):
258 if self._parent._has_key(name):
259 return self.__getattr__(name)
260 else:
262 def meth(*arg, **kw):
263 return getattr(collections_abc.Sequence, name)(
264 self, *arg, **kw
265 )
267 return meth
269 return go
271 count = _special_name_accessor("count")
272 index = _special_name_accessor("index")
274 def __contains__(self, key):
275 return key in self._data
277 def __getstate__(self):
278 return {
279 "_parent": self._parent,
280 "_data": self._data,
281 "_key_style": self._key_style,
282 }
284 def __setstate__(self, state):
285 parent = state["_parent"]
286 object.__setattr__(self, "_parent", parent)
287 object.__setattr__(self, "_data", state["_data"])
288 object.__setattr__(self, "_keymap", parent._keymap)
289 object.__setattr__(self, "_key_style", state["_key_style"])
291 def _op(self, other, op):
292 return (
293 op(tuple(self), tuple(other))
294 if isinstance(other, Row)
295 else op(tuple(self), other)
296 )
298 __hash__ = BaseRow.__hash__
300 def __lt__(self, other):
301 return self._op(other, operator.lt)
303 def __le__(self, other):
304 return self._op(other, operator.le)
306 def __ge__(self, other):
307 return self._op(other, operator.ge)
309 def __gt__(self, other):
310 return self._op(other, operator.gt)
312 def __eq__(self, other):
313 return self._op(other, operator.eq)
315 def __ne__(self, other):
316 return self._op(other, operator.ne)
318 def __repr__(self):
319 return repr(sql_util._repr_row(self))
321 @util.deprecated_20(
322 ":meth:`.Row.keys`",
323 alternative="Use the namedtuple standard accessor "
324 ":attr:`.Row._fields`, or for full mapping behavior use "
325 "row._mapping.keys() ",
326 )
327 def keys(self):
328 """Return the list of keys as strings represented by this
329 :class:`.Row`.
331 The keys can represent the labels of the columns returned by a core
332 statement or the names of the orm classes returned by an orm
333 execution.
335 This method is analogous to the Python dictionary ``.keys()`` method,
336 except that it returns a list, not an iterator.
338 .. seealso::
340 :attr:`.Row._fields`
342 :attr:`.Row._mapping`
344 """
345 return self._parent.keys
347 @property
348 def _fields(self):
349 """Return a tuple of string keys as represented by this
350 :class:`.Row`.
352 The keys can represent the labels of the columns returned by a core
353 statement or the names of the orm classes returned by an orm
354 execution.
356 This attribute is analogous to the Python named tuple ``._fields``
357 attribute.
359 .. versionadded:: 1.4
361 .. seealso::
363 :attr:`.Row._mapping`
365 """
366 return tuple([k for k in self._parent.keys if k is not None])
368 def _asdict(self):
369 """Return a new dict which maps field names to their corresponding
370 values.
372 This method is analogous to the Python named tuple ``._asdict()``
373 method, and works by applying the ``dict()`` constructor to the
374 :attr:`.Row._mapping` attribute.
376 .. versionadded:: 1.4
378 .. seealso::
380 :attr:`.Row._mapping`
382 """
383 return dict(self._mapping)
385 def _replace(self):
386 raise NotImplementedError()
388 @property
389 def _field_defaults(self):
390 raise NotImplementedError()
393class LegacyRow(Row):
394 """A subclass of :class:`.Row` that delivers 1.x SQLAlchemy behaviors
395 for Core.
397 The :class:`.LegacyRow` class is where most of the Python mapping
398 (i.e. dictionary-like)
399 behaviors are implemented for the row object. The mapping behavior
400 of :class:`.Row` going forward is accessible via the :class:`.Row._mapping`
401 attribute.
403 .. versionadded:: 1.4 - added :class:`.LegacyRow` which encapsulates most
404 of the deprecated behaviors of :class:`.Row`.
406 """
408 __slots__ = ()
410 if util.SQLALCHEMY_WARN_20:
411 _default_key_style = KEY_OBJECTS_BUT_WARN
412 else:
413 _default_key_style = KEY_OBJECTS_NO_WARN
415 def __contains__(self, key):
416 return self._parent._contains(key, self)
418 # prior to #6218, LegacyRow would redirect the behavior of __getitem__
419 # for the non C version of BaseRow. This is now set up by Python BaseRow
420 # in all cases
421 # if not _baserow_usecext:
422 # __getitem__ = BaseRow._get_by_key_impl
424 @util.deprecated(
425 "1.4",
426 "The :meth:`.LegacyRow.has_key` method is deprecated and will be "
427 "removed in a future release. To test for key membership, use "
428 "the :attr:`Row._mapping` attribute, i.e. 'key in row._mapping`.",
429 )
430 def has_key(self, key):
431 """Return True if this :class:`.LegacyRow` contains the given key.
433 Through the SQLAlchemy 1.x series, the ``__contains__()`` method of
434 :class:`.Row` (or :class:`.LegacyRow` as of SQLAlchemy 1.4) also links
435 to :meth:`.Row.has_key`, in that an expression such as ::
437 "some_col" in row
439 Will return True if the row contains a column named ``"some_col"``,
440 in the way that a Python mapping works.
442 However, it is planned that the 2.0 series of SQLAlchemy will reverse
443 this behavior so that ``__contains__()`` will refer to a value being
444 present in the row, in the way that a Python tuple works.
446 .. seealso::
448 :ref:`change_4710_core`
450 """
452 return self._parent._has_key(key)
454 @util.deprecated(
455 "1.4",
456 "The :meth:`.LegacyRow.items` method is deprecated and will be "
457 "removed in a future release. Use the :attr:`Row._mapping` "
458 "attribute, i.e., 'row._mapping.items()'.",
459 )
460 def items(self):
461 """Return a list of tuples, each tuple containing a key/value pair.
463 This method is analogous to the Python dictionary ``.items()`` method,
464 except that it returns a list, not an iterator.
466 """
468 return [(key, self[key]) for key in self.keys()]
470 @util.deprecated(
471 "1.4",
472 "The :meth:`.LegacyRow.iterkeys` method is deprecated and will be "
473 "removed in a future release. Use the :attr:`Row._mapping` "
474 "attribute, i.e., 'row._mapping.keys()'.",
475 )
476 def iterkeys(self):
477 """Return a an iterator against the :meth:`.Row.keys` method.
479 This method is analogous to the Python-2-only dictionary
480 ``.iterkeys()`` method.
482 """
483 return iter(self._parent.keys)
485 @util.deprecated(
486 "1.4",
487 "The :meth:`.LegacyRow.itervalues` method is deprecated and will be "
488 "removed in a future release. Use the :attr:`Row._mapping` "
489 "attribute, i.e., 'row._mapping.values()'.",
490 )
491 def itervalues(self):
492 """Return a an iterator against the :meth:`.Row.values` method.
494 This method is analogous to the Python-2-only dictionary
495 ``.itervalues()`` method.
497 """
498 return iter(self)
500 @util.deprecated(
501 "1.4",
502 "The :meth:`.LegacyRow.values` method is deprecated and will be "
503 "removed in a future release. Use the :attr:`Row._mapping` "
504 "attribute, i.e., 'row._mapping.values()'.",
505 )
506 def values(self):
507 """Return the values represented by this :class:`.Row` as a list.
509 This method is analogous to the Python dictionary ``.values()`` method,
510 except that it returns a list, not an iterator.
512 """
514 return self._values_impl()
517BaseRowProxy = BaseRow
518RowProxy = Row
521class ROMappingView(
522 collections_abc.KeysView,
523 collections_abc.ValuesView,
524 collections_abc.ItemsView,
525):
526 __slots__ = (
527 "_mapping",
528 "_items",
529 )
531 def __init__(self, mapping, items):
532 self._mapping = mapping
533 self._items = items
535 def __len__(self):
536 return len(self._items)
538 def __repr__(self):
539 return "{0.__class__.__name__}({0._mapping!r})".format(self)
541 def __iter__(self):
542 return iter(self._items)
544 def __contains__(self, item):
545 return item in self._items
547 def __eq__(self, other):
548 return list(other) == list(self)
550 def __ne__(self, other):
551 return list(other) != list(self)
554class RowMapping(BaseRow, collections_abc.Mapping):
555 """A ``Mapping`` that maps column names and objects to :class:`.Row`
556 values.
558 The :class:`.RowMapping` is available from a :class:`.Row` via the
559 :attr:`.Row._mapping` attribute, as well as from the iterable interface
560 provided by the :class:`.MappingResult` object returned by the
561 :meth:`_engine.Result.mappings` method.
563 :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
564 the contents of the row. This includes support for testing of
565 containment of specific keys (string column names or objects), as well
566 as iteration of keys, values, and items::
568 for row in result:
569 if 'a' in row._mapping:
570 print("Column 'a': %s" % row._mapping['a'])
572 print("Column b: %s" % row._mapping[table.c.b])
575 .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the
576 mapping-like access previously provided by a database result row,
577 which now seeks to behave mostly like a named tuple.
579 """
581 __slots__ = ()
583 _default_key_style = KEY_OBJECTS_ONLY
585 if not _baserow_usecext:
587 __getitem__ = BaseRow._get_by_key_impl_mapping
589 def _values_impl(self):
590 return list(self._data)
592 def __iter__(self):
593 return (k for k in self._parent.keys if k is not None)
595 def __len__(self):
596 return len(self._data)
598 def __contains__(self, key):
599 return self._parent._has_key(key)
601 def __repr__(self):
602 return repr(dict(self))
604 def items(self):
605 """Return a view of key/value tuples for the elements in the
606 underlying :class:`.Row`.
608 """
609 return ROMappingView(self, [(key, self[key]) for key in self.keys()])
611 def keys(self):
612 """Return a view of 'keys' for string column names represented
613 by the underlying :class:`.Row`.
615 """
617 return self._parent.keys
619 def values(self):
620 """Return a view of values for the values represented in the
621 underlying :class:`.Row`.
623 """
624 return ROMappingView(self, self._values_impl())