Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/row.py: 42%
206 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# engine/row.py
2# Copyright (C) 2005-2022 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 rec = self._parent._key_fallback(key, ke)
134 except TypeError:
135 if isinstance(key, slice):
136 return tuple(self._data[key])
137 else:
138 raise
140 mdindex = rec[MD_INDEX]
141 if mdindex is None:
142 self._parent._raise_for_ambiguous_column_name(rec)
144 elif self._key_style == KEY_OBJECTS_BUT_WARN and mdindex != key:
145 self._parent._warn_for_nonint(key)
147 return self._data[mdindex]
149 # The original 1.4 plan was that Row would not allow row["str"]
150 # access, however as the C extensions were inadvertently allowing
151 # this coupled with the fact that orm Session sets future=True,
152 # this allows a softer upgrade path. see #6218
153 __getitem__ = _get_by_key_impl
155 def _get_by_key_impl_mapping(self, key):
156 try:
157 rec = self._keymap[key]
158 except KeyError as ke:
159 rec = self._parent._key_fallback(key, ke)
161 mdindex = rec[MD_INDEX]
162 if mdindex is None:
163 self._parent._raise_for_ambiguous_column_name(rec)
164 elif (
165 self._key_style == KEY_OBJECTS_ONLY
166 and int in key.__class__.__mro__
167 ):
168 raise KeyError(key)
170 return self._data[mdindex]
172 def __getattr__(self, name):
173 try:
174 return self._get_by_key_impl_mapping(name)
175 except KeyError as e:
176 util.raise_(AttributeError(e.args[0]), replace_context=e)
179class Row(BaseRow, collections_abc.Sequence):
180 """Represent a single result row.
182 The :class:`.Row` object represents a row of a database result. It is
183 typically associated in the 1.x series of SQLAlchemy with the
184 :class:`_engine.CursorResult` object, however is also used by the ORM for
185 tuple-like results as of SQLAlchemy 1.4.
187 The :class:`.Row` object seeks to act as much like a Python named
188 tuple as possible. For mapping (i.e. dictionary) behavior on a row,
189 such as testing for containment of keys, refer to the :attr:`.Row._mapping`
190 attribute.
192 .. seealso::
194 :ref:`tutorial_selecting_data` - includes examples of selecting
195 rows from SELECT statements.
197 :class:`.LegacyRow` - Compatibility interface introduced in SQLAlchemy
198 1.4.
200 .. versionchanged:: 1.4
202 Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a
203 "proxy" object in that it contains the final form of data within it,
204 and now acts mostly like a named tuple. Mapping-like functionality is
205 moved to the :attr:`.Row._mapping` attribute, but will remain available
206 in SQLAlchemy 1.x series via the :class:`.LegacyRow` class that is used
207 by :class:`_engine.LegacyCursorResult`.
208 See :ref:`change_4710_core` for background
209 on this change.
211 """
213 __slots__ = ()
215 # in 2.0, this should be KEY_INTEGER_ONLY
216 _default_key_style = KEY_OBJECTS_BUT_WARN
218 def __setattr__(self, name, value):
219 raise AttributeError("can't set attribute")
221 def __delattr__(self, name):
222 raise AttributeError("can't delete attribute")
224 @property
225 def _mapping(self):
226 """Return a :class:`.RowMapping` for this :class:`.Row`.
228 This object provides a consistent Python mapping (i.e. dictionary)
229 interface for the data contained within the row. The :class:`.Row`
230 by itself behaves like a named tuple, however in the 1.4 series of
231 SQLAlchemy, the :class:`.LegacyRow` class is still used by Core which
232 continues to have mapping-like behaviors against the row object
233 itself.
235 .. seealso::
237 :attr:`.Row._fields`
239 .. versionadded:: 1.4
241 """
242 return RowMapping(
243 self._parent,
244 None,
245 self._keymap,
246 RowMapping._default_key_style,
247 self._data,
248 )
250 def _special_name_accessor(name):
251 """Handle ambiguous names such as "count" and "index" """
253 @property
254 def go(self):
255 if self._parent._has_key(name):
256 return self.__getattr__(name)
257 else:
259 def meth(*arg, **kw):
260 return getattr(collections_abc.Sequence, name)(
261 self, *arg, **kw
262 )
264 return meth
266 return go
268 count = _special_name_accessor("count")
269 index = _special_name_accessor("index")
271 def __contains__(self, key):
272 return key in self._data
274 def __getstate__(self):
275 return {
276 "_parent": self._parent,
277 "_data": self._data,
278 "_key_style": self._key_style,
279 }
281 def __setstate__(self, state):
282 parent = state["_parent"]
283 object.__setattr__(self, "_parent", parent)
284 object.__setattr__(self, "_data", state["_data"])
285 object.__setattr__(self, "_keymap", parent._keymap)
286 object.__setattr__(self, "_key_style", state["_key_style"])
288 def _op(self, other, op):
289 return (
290 op(tuple(self), tuple(other))
291 if isinstance(other, Row)
292 else op(tuple(self), other)
293 )
295 __hash__ = BaseRow.__hash__
297 def __lt__(self, other):
298 return self._op(other, operator.lt)
300 def __le__(self, other):
301 return self._op(other, operator.le)
303 def __ge__(self, other):
304 return self._op(other, operator.ge)
306 def __gt__(self, other):
307 return self._op(other, operator.gt)
309 def __eq__(self, other):
310 return self._op(other, operator.eq)
312 def __ne__(self, other):
313 return self._op(other, operator.ne)
315 def __repr__(self):
316 return repr(sql_util._repr_row(self))
318 @util.deprecated_20(
319 ":meth:`.Row.keys`",
320 alternative="Use the namedtuple standard accessor "
321 ":attr:`.Row._fields`, or for full mapping behavior use "
322 "row._mapping.keys() ",
323 )
324 def keys(self):
325 """Return the list of keys as strings represented by this
326 :class:`.Row`.
328 The keys can represent the labels of the columns returned by a core
329 statement or the names of the orm classes returned by an orm
330 execution.
332 This method is analogous to the Python dictionary ``.keys()`` method,
333 except that it returns a list, not an iterator.
335 .. seealso::
337 :attr:`.Row._fields`
339 :attr:`.Row._mapping`
341 """
342 return self._parent.keys
344 @property
345 def _fields(self):
346 """Return a tuple of string keys as represented by this
347 :class:`.Row`.
349 The keys can represent the labels of the columns returned by a core
350 statement or the names of the orm classes returned by an orm
351 execution.
353 This attribute is analogous to the Python named tuple ``._fields``
354 attribute.
356 .. versionadded:: 1.4
358 .. seealso::
360 :attr:`.Row._mapping`
362 """
363 return tuple([k for k in self._parent.keys if k is not None])
365 def _asdict(self):
366 """Return a new dict which maps field names to their corresponding
367 values.
369 This method is analogous to the Python named tuple ``._asdict()``
370 method, and works by applying the ``dict()`` constructor to the
371 :attr:`.Row._mapping` attribute.
373 .. versionadded:: 1.4
375 .. seealso::
377 :attr:`.Row._mapping`
379 """
380 return dict(self._mapping)
382 def _replace(self):
383 raise NotImplementedError()
385 @property
386 def _field_defaults(self):
387 raise NotImplementedError()
390class LegacyRow(Row):
391 """A subclass of :class:`.Row` that delivers 1.x SQLAlchemy behaviors
392 for Core.
394 The :class:`.LegacyRow` class is where most of the Python mapping
395 (i.e. dictionary-like)
396 behaviors are implemented for the row object. The mapping behavior
397 of :class:`.Row` going forward is accessible via the :class:`.Row._mapping`
398 attribute.
400 .. versionadded:: 1.4 - added :class:`.LegacyRow` which encapsulates most
401 of the deprecated behaviors of :class:`.Row`.
403 """
405 __slots__ = ()
407 if util.SQLALCHEMY_WARN_20:
408 _default_key_style = KEY_OBJECTS_BUT_WARN
409 else:
410 _default_key_style = KEY_OBJECTS_NO_WARN
412 def __contains__(self, key):
413 return self._parent._contains(key, self)
415 # prior to #6218, LegacyRow would redirect the behavior of __getitem__
416 # for the non C version of BaseRow. This is now set up by Python BaseRow
417 # in all cases
418 # if not _baserow_usecext:
419 # __getitem__ = BaseRow._get_by_key_impl
421 @util.deprecated(
422 "1.4",
423 "The :meth:`.LegacyRow.has_key` method is deprecated and will be "
424 "removed in a future release. To test for key membership, use "
425 "the :attr:`Row._mapping` attribute, i.e. 'key in row._mapping`.",
426 )
427 def has_key(self, key):
428 """Return True if this :class:`.LegacyRow` contains the given key.
430 Through the SQLAlchemy 1.x series, the ``__contains__()`` method of
431 :class:`.Row` (or :class:`.LegacyRow` as of SQLAlchemy 1.4) also links
432 to :meth:`.Row.has_key`, in that an expression such as ::
434 "some_col" in row
436 Will return True if the row contains a column named ``"some_col"``,
437 in the way that a Python mapping works.
439 However, it is planned that the 2.0 series of SQLAlchemy will reverse
440 this behavior so that ``__contains__()`` will refer to a value being
441 present in the row, in the way that a Python tuple works.
443 .. seealso::
445 :ref:`change_4710_core`
447 """
449 return self._parent._has_key(key)
451 @util.deprecated(
452 "1.4",
453 "The :meth:`.LegacyRow.items` method is deprecated and will be "
454 "removed in a future release. Use the :attr:`Row._mapping` "
455 "attribute, i.e., 'row._mapping.items()'.",
456 )
457 def items(self):
458 """Return a list of tuples, each tuple containing a key/value pair.
460 This method is analogous to the Python dictionary ``.items()`` method,
461 except that it returns a list, not an iterator.
463 """
465 return [(key, self[key]) for key in self.keys()]
467 @util.deprecated(
468 "1.4",
469 "The :meth:`.LegacyRow.iterkeys` method is deprecated and will be "
470 "removed in a future release. Use the :attr:`Row._mapping` "
471 "attribute, i.e., 'row._mapping.keys()'.",
472 )
473 def iterkeys(self):
474 """Return a an iterator against the :meth:`.Row.keys` method.
476 This method is analogous to the Python-2-only dictionary
477 ``.iterkeys()`` method.
479 """
480 return iter(self._parent.keys)
482 @util.deprecated(
483 "1.4",
484 "The :meth:`.LegacyRow.itervalues` method is deprecated and will be "
485 "removed in a future release. Use the :attr:`Row._mapping` "
486 "attribute, i.e., 'row._mapping.values()'.",
487 )
488 def itervalues(self):
489 """Return a an iterator against the :meth:`.Row.values` method.
491 This method is analogous to the Python-2-only dictionary
492 ``.itervalues()`` method.
494 """
495 return iter(self)
497 @util.deprecated(
498 "1.4",
499 "The :meth:`.LegacyRow.values` method is deprecated and will be "
500 "removed in a future release. Use the :attr:`Row._mapping` "
501 "attribute, i.e., 'row._mapping.values()'.",
502 )
503 def values(self):
504 """Return the values represented by this :class:`.Row` as a list.
506 This method is analogous to the Python dictionary ``.values()`` method,
507 except that it returns a list, not an iterator.
509 """
511 return self._values_impl()
514BaseRowProxy = BaseRow
515RowProxy = Row
518class ROMappingView(
519 collections_abc.KeysView,
520 collections_abc.ValuesView,
521 collections_abc.ItemsView,
522):
523 __slots__ = (
524 "_mapping",
525 "_items",
526 )
528 def __init__(self, mapping, items):
529 self._mapping = mapping
530 self._items = items
532 def __len__(self):
533 return len(self._items)
535 def __repr__(self):
536 return "{0.__class__.__name__}({0._mapping!r})".format(self)
538 def __iter__(self):
539 return iter(self._items)
541 def __contains__(self, item):
542 return item in self._items
544 def __eq__(self, other):
545 return list(other) == list(self)
547 def __ne__(self, other):
548 return list(other) != list(self)
551class RowMapping(BaseRow, collections_abc.Mapping):
552 """A ``Mapping`` that maps column names and objects to :class:`.Row`
553 values.
555 The :class:`.RowMapping` is available from a :class:`.Row` via the
556 :attr:`.Row._mapping` attribute, as well as from the iterable interface
557 provided by the :class:`.MappingResult` object returned by the
558 :meth:`_engine.Result.mappings` method.
560 :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
561 the contents of the row. This includes support for testing of
562 containment of specific keys (string column names or objects), as well
563 as iteration of keys, values, and items::
565 for row in result:
566 if 'a' in row._mapping:
567 print("Column 'a': %s" % row._mapping['a'])
569 print("Column b: %s" % row._mapping[table.c.b])
572 .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the
573 mapping-like access previously provided by a database result row,
574 which now seeks to behave mostly like a named tuple.
576 """
578 __slots__ = ()
580 _default_key_style = KEY_OBJECTS_ONLY
582 if not _baserow_usecext:
584 __getitem__ = BaseRow._get_by_key_impl_mapping
586 def _values_impl(self):
587 return list(self._data)
589 def __iter__(self):
590 return (k for k in self._parent.keys if k is not None)
592 def __len__(self):
593 return len(self._data)
595 def __contains__(self, key):
596 return self._parent._has_key(key)
598 def __repr__(self):
599 return repr(dict(self))
601 def items(self):
602 """Return a view of key/value tuples for the elements in the
603 underlying :class:`.Row`.
605 """
606 return ROMappingView(self, [(key, self[key]) for key in self.keys()])
608 def keys(self):
609 """Return a view of 'keys' for string column names represented
610 by the underlying :class:`.Row`.
612 """
614 return self._parent.keys
616 def values(self):
617 """Return a view of values for the values represented in the
618 underlying :class:`.Row`.
620 """
621 return ROMappingView(self, self._values_impl())