1# engine/row.py 2# Copyright (C) 2005-2021 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 10 11import operator 12 13from .. import util 14from ..sql import util as sql_util 15from ..util.compat import collections_abc 16 17MD_INDEX = 0 # integer index in cursor.description 18 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 26 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) 32 33 34except ImportError: 35 36 def rowproxy_reconstructor(cls, state): 37 obj = cls.__new__(cls) 38 obj.__setstate__(state) 39 return obj 40 41 42KEY_INTEGER_ONLY = 0 43"""__getitem__ only allows integer values, raises TypeError otherwise""" 44 45KEY_OBJECTS_ONLY = 1 46"""__getitem__ only allows string/object values, raises TypeError otherwise""" 47 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""" 51 52KEY_OBJECTS_NO_WARN = 3 53"""__getitem__ allows integer or string/object values with no warnings 54or errors.""" 55 56try: 57 from sqlalchemy.cresultproxy import BaseRow 58 59 _baserow_usecext = True 60except ImportError: 61 _baserow_usecext = False 62 63 class BaseRow(object): 64 __slots__ = ("_parent", "_data", "_keymap", "_key_style") 65 66 def __init__(self, parent, processors, keymap, key_style, data): 67 """Row objects are constructed by CursorResult objects.""" 68 69 self._parent = parent 70 71 if processors: 72 self._data = tuple( 73 [ 74 proc(value) if proc else value 75 for proc, value in zip(processors, data) 76 ] 77 ) 78 else: 79 self._data = tuple(data) 80 81 self._keymap = keymap 82 83 self._key_style = key_style 84 85 def __reduce__(self): 86 return ( 87 rowproxy_reconstructor, 88 (self.__class__, self.__getstate__()), 89 ) 90 91 def _filter_on_values(self, filters): 92 return Row( 93 self._parent, 94 filters, 95 self._keymap, 96 self._key_style, 97 self._data, 98 ) 99 100 def _values_impl(self): 101 return list(self) 102 103 def __iter__(self): 104 return iter(self._data) 105 106 def __len__(self): 107 return len(self._data) 108 109 def __hash__(self): 110 return hash(self._data) 111 112 def _get_by_int_impl(self, key): 113 return self._data[key] 114 115 def _get_by_key_impl(self, key): 116 if int in key.__class__.__mro__: 117 return self._data[key] 118 119 if self._key_style == KEY_INTEGER_ONLY: 120 self._parent._raise_for_nonint(key) 121 122 # the following is all LegacyRow support. none of this 123 # should be called if not LegacyRow 124 # assert isinstance(self, LegacyRow) 125 126 try: 127 rec = self._keymap[key] 128 except KeyError as ke: 129 rec = self._parent._key_fallback(key, ke) 130 except TypeError: 131 if isinstance(key, slice): 132 return tuple(self._data[key]) 133 else: 134 raise 135 136 mdindex = rec[MD_INDEX] 137 if mdindex is None: 138 self._parent._raise_for_ambiguous_column_name(rec) 139 140 elif self._key_style == KEY_OBJECTS_BUT_WARN and mdindex != key: 141 self._parent._warn_for_nonint(key) 142 143 return self._data[mdindex] 144 145 # The original 1.4 plan was that Row would not allow row["str"] 146 # access, however as the C extensions were inadvertently allowing 147 # this coupled with the fact that orm Session sets future=True, 148 # this allows a softer upgrade path. see #6218 149 __getitem__ = _get_by_key_impl 150 151 def _get_by_key_impl_mapping(self, key): 152 try: 153 rec = self._keymap[key] 154 except KeyError as ke: 155 rec = self._parent._key_fallback(key, ke) 156 157 mdindex = rec[MD_INDEX] 158 if mdindex is None: 159 self._parent._raise_for_ambiguous_column_name(rec) 160 elif ( 161 self._key_style == KEY_OBJECTS_ONLY 162 and int in key.__class__.__mro__ 163 ): 164 raise KeyError(key) 165 166 return self._data[mdindex] 167 168 def __getattr__(self, name): 169 try: 170 return self._get_by_key_impl_mapping(name) 171 except KeyError as e: 172 util.raise_(AttributeError(e.args[0]), replace_context=e) 173 174 175class Row(BaseRow, collections_abc.Sequence): 176 """Represent a single result row. 177 178 The :class:`.Row` object represents a row of a database result. It is 179 typically associated in the 1.x series of SQLAlchemy with the 180 :class:`_engine.CursorResult` object, however is also used by the ORM for 181 tuple-like results as of SQLAlchemy 1.4. 182 183 The :class:`.Row` object seeks to act as much like a Python named 184 tuple as possible. For mapping (i.e. dictionary) behavior on a row, 185 such as testing for containment of keys, refer to the :attr:`.Row._mapping` 186 attribute. 187 188 .. seealso:: 189 190 :ref:`coretutorial_selecting` - includes examples of selecting 191 rows from SELECT statements. 192 193 :class:`.LegacyRow` - Compatibility interface introduced in SQLAlchemy 194 1.4. 195 196 .. versionchanged:: 1.4 197 198 Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a 199 "proxy" object in that it contains the final form of data within it, 200 and now acts mostly like a named tuple. Mapping-like functionality is 201 moved to the :attr:`.Row._mapping` attribute, but will remain available 202 in SQLAlchemy 1.x series via the :class:`.LegacyRow` class that is used 203 by :class:`_engine.LegacyCursorResult`. 204 See :ref:`change_4710_core` for background 205 on this change. 206 207 """ 208 209 __slots__ = () 210 211 # in 2.0, this should be KEY_INTEGER_ONLY 212 _default_key_style = KEY_OBJECTS_BUT_WARN 213 214 @property 215 def _mapping(self): 216 """Return a :class:`.RowMapping` for this :class:`.Row`. 217 218 This object provides a consistent Python mapping (i.e. dictionary) 219 interface for the data contained within the row. The :class:`.Row` 220 by itself behaves like a named tuple, however in the 1.4 series of 221 SQLAlchemy, the :class:`.LegacyRow` class is still used by Core which 222 continues to have mapping-like behaviors against the row object 223 itself. 224 225 .. seealso:: 226 227 :attr:`.Row._fields` 228 229 .. versionadded:: 1.4 230 231 """ 232 return RowMapping( 233 self._parent, 234 None, 235 self._keymap, 236 RowMapping._default_key_style, 237 self._data, 238 ) 239 240 def _special_name_accessor(name): 241 """Handle ambiguous names such as "count" and "index" """ 242 243 @property 244 def go(self): 245 if self._parent._has_key(name): 246 return self.__getattr__(name) 247 else: 248 249 def meth(*arg, **kw): 250 return getattr(collections_abc.Sequence, name)( 251 self, *arg, **kw 252 ) 253 254 return meth 255 256 return go 257 258 count = _special_name_accessor("count") 259 index = _special_name_accessor("index") 260 261 def __contains__(self, key): 262 return key in self._data 263 264 def __getstate__(self): 265 return { 266 "_parent": self._parent, 267 "_data": self._data, 268 "_key_style": self._key_style, 269 } 270 271 def __setstate__(self, state): 272 self._parent = parent = state["_parent"] 273 self._data = state["_data"] 274 self._keymap = parent._keymap 275 self._key_style = state["_key_style"] 276 277 def _op(self, other, op): 278 return ( 279 op(tuple(self), tuple(other)) 280 if isinstance(other, Row) 281 else op(tuple(self), other) 282 ) 283 284 __hash__ = BaseRow.__hash__ 285 286 def __lt__(self, other): 287 return self._op(other, operator.lt) 288 289 def __le__(self, other): 290 return self._op(other, operator.le) 291 292 def __ge__(self, other): 293 return self._op(other, operator.ge) 294 295 def __gt__(self, other): 296 return self._op(other, operator.gt) 297 298 def __eq__(self, other): 299 return self._op(other, operator.eq) 300 301 def __ne__(self, other): 302 return self._op(other, operator.ne) 303 304 def __repr__(self): 305 return repr(sql_util._repr_row(self)) 306 307 @util.deprecated_20( 308 ":meth:`.Row.keys`", 309 alternative="Use the namedtuple standard accessor " 310 ":attr:`.Row._fields`, or for full mapping behavior use " 311 "row._mapping.keys() ", 312 ) 313 def keys(self): 314 """Return the list of keys as strings represented by this 315 :class:`.Row`. 316 317 The keys can represent the labels of the columns returned by a core 318 statement or the names of the orm classes returned by an orm 319 execution. 320 321 This method is analogous to the Python dictionary ``.keys()`` method, 322 except that it returns a list, not an iterator. 323 324 .. seealso:: 325 326 :attr:`.Row._fields` 327 328 :attr:`.Row._mapping` 329 330 """ 331 return self._parent.keys 332 333 @property 334 def _fields(self): 335 """Return a tuple of string keys as represented by this 336 :class:`.Row`. 337 338 The keys can represent the labels of the columns returned by a core 339 statement or the names of the orm classes returned by an orm 340 execution. 341 342 This attribute is analogous to the Python named tuple ``._fields`` 343 attribute. 344 345 .. versionadded:: 1.4 346 347 .. seealso:: 348 349 :attr:`.Row._mapping` 350 351 """ 352 return tuple([k for k in self._parent.keys if k is not None]) 353 354 def _asdict(self): 355 """Return a new dict which maps field names to their corresponding 356 values. 357 358 This method is analogous to the Python named tuple ``._asdict()`` 359 method, and works by applying the ``dict()`` constructor to the 360 :attr:`.Row._mapping` attribute. 361 362 .. versionadded:: 1.4 363 364 .. seealso:: 365 366 :attr:`.Row._mapping` 367 368 """ 369 return dict(self._mapping) 370 371 def _replace(self): 372 raise NotImplementedError() 373 374 @property 375 def _field_defaults(self): 376 raise NotImplementedError() 377 378 379class LegacyRow(Row): 380 """A subclass of :class:`.Row` that delivers 1.x SQLAlchemy behaviors 381 for Core. 382 383 The :class:`.LegacyRow` class is where most of the Python mapping 384 (i.e. dictionary-like) 385 behaviors are implemented for the row object. The mapping behavior 386 of :class:`.Row` going forward is accessible via the :class:`.Row._mapping` 387 attribute. 388 389 .. versionadded:: 1.4 - added :class:`.LegacyRow` which encapsulates most 390 of the deprecated behaviors of :class:`.Row`. 391 392 """ 393 394 __slots__ = () 395 396 if util.SQLALCHEMY_WARN_20: 397 _default_key_style = KEY_OBJECTS_BUT_WARN 398 else: 399 _default_key_style = KEY_OBJECTS_NO_WARN 400 401 def __contains__(self, key): 402 return self._parent._contains(key, self) 403 404 # prior to #6218, LegacyRow would redirect the behavior of __getitem__ 405 # for the non C version of BaseRow. This is now set up by Python BaseRow 406 # in all cases 407 # if not _baserow_usecext: 408 # __getitem__ = BaseRow._get_by_key_impl 409 410 @util.deprecated( 411 "1.4", 412 "The :meth:`.LegacyRow.has_key` method is deprecated and will be " 413 "removed in a future release. To test for key membership, use " 414 "the :attr:`Row._mapping` attribute, i.e. 'key in row._mapping`.", 415 ) 416 def has_key(self, key): 417 """Return True if this :class:`.LegacyRow` contains the given key. 418 419 Through the SQLAlchemy 1.x series, the ``__contains__()`` method of 420 :class:`.Row` (or :class:`.LegacyRow` as of SQLAlchemy 1.4) also links 421 to :meth:`.Row.has_key`, in that an expression such as :: 422 423 "some_col" in row 424 425 Will return True if the row contains a column named ``"some_col"``, 426 in the way that a Python mapping works. 427 428 However, it is planned that the 2.0 series of SQLAlchemy will reverse 429 this behavior so that ``__contains__()`` will refer to a value being 430 present in the row, in the way that a Python tuple works. 431 432 .. seealso:: 433 434 :ref:`change_4710_core` 435 436 """ 437 438 return self._parent._has_key(key) 439 440 @util.deprecated( 441 "1.4", 442 "The :meth:`.LegacyRow.items` method is deprecated and will be " 443 "removed in a future release. Use the :attr:`Row._mapping` " 444 "attribute, i.e., 'row._mapping.items()'.", 445 ) 446 def items(self): 447 """Return a list of tuples, each tuple containing a key/value pair. 448 449 This method is analogous to the Python dictionary ``.items()`` method, 450 except that it returns a list, not an iterator. 451 452 """ 453 454 return [(key, self[key]) for key in self.keys()] 455 456 @util.deprecated( 457 "1.4", 458 "The :meth:`.LegacyRow.iterkeys` method is deprecated and will be " 459 "removed in a future release. Use the :attr:`Row._mapping` " 460 "attribute, i.e., 'row._mapping.keys()'.", 461 ) 462 def iterkeys(self): 463 """Return a an iterator against the :meth:`.Row.keys` method. 464 465 This method is analogous to the Python-2-only dictionary 466 ``.iterkeys()`` method. 467 468 """ 469 return iter(self._parent.keys) 470 471 @util.deprecated( 472 "1.4", 473 "The :meth:`.LegacyRow.itervalues` method is deprecated and will be " 474 "removed in a future release. Use the :attr:`Row._mapping` " 475 "attribute, i.e., 'row._mapping.values()'.", 476 ) 477 def itervalues(self): 478 """Return a an iterator against the :meth:`.Row.values` method. 479 480 This method is analogous to the Python-2-only dictionary 481 ``.itervalues()`` method. 482 483 """ 484 return iter(self) 485 486 @util.deprecated( 487 "1.4", 488 "The :meth:`.LegacyRow.values` method is deprecated and will be " 489 "removed in a future release. Use the :attr:`Row._mapping` " 490 "attribute, i.e., 'row._mapping.values()'.", 491 ) 492 def values(self): 493 """Return the values represented by this :class:`.Row` as a list. 494 495 This method is analogous to the Python dictionary ``.values()`` method, 496 except that it returns a list, not an iterator. 497 498 """ 499 500 return self._values_impl() 501 502 503BaseRowProxy = BaseRow 504RowProxy = Row 505 506 507class ROMappingView( 508 collections_abc.KeysView, 509 collections_abc.ValuesView, 510 collections_abc.ItemsView, 511): 512 __slots__ = ( 513 "_mapping", 514 "_items", 515 ) 516 517 def __init__(self, mapping, items): 518 self._mapping = mapping 519 self._items = items 520 521 def __len__(self): 522 return len(self._items) 523 524 def __repr__(self): 525 return "{0.__class__.__name__}({0._mapping!r})".format(self) 526 527 def __iter__(self): 528 return iter(self._items) 529 530 def __contains__(self, item): 531 return item in self._items 532 533 def __eq__(self, other): 534 return list(other) == list(self) 535 536 def __ne__(self, other): 537 return list(other) != list(self) 538 539 540class RowMapping(BaseRow, collections_abc.Mapping): 541 """A ``Mapping`` that maps column names and objects to :class:`.Row` values. 542 543 The :class:`.RowMapping` is available from a :class:`.Row` via the 544 :attr:`.Row._mapping` attribute, as well as from the iterable interface 545 provided by the :class:`.MappingResult` object returned by the 546 :meth:`_engine.Result.mappings` method. 547 548 :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to 549 the contents of the row. This includes support for testing of 550 containment of specific keys (string column names or objects), as well 551 as iteration of keys, values, and items:: 552 553 for row in result: 554 if 'a' in row._mapping: 555 print("Column 'a': %s" % row._mapping['a']) 556 557 print("Column b: %s" % row._mapping[table.c.b]) 558 559 560 .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the 561 mapping-like access previously provided by a database result row, 562 which now seeks to behave mostly like a named tuple. 563 564 """ 565 566 __slots__ = () 567 568 _default_key_style = KEY_OBJECTS_ONLY 569 570 if not _baserow_usecext: 571 572 __getitem__ = BaseRow._get_by_key_impl_mapping 573 574 def _values_impl(self): 575 return list(self._data) 576 577 def __iter__(self): 578 return (k for k in self._parent.keys if k is not None) 579 580 def __len__(self): 581 return len(self._data) 582 583 def __contains__(self, key): 584 return self._parent._has_key(key) 585 586 def __repr__(self): 587 return repr(dict(self)) 588 589 def items(self): 590 """Return a view of key/value tuples for the elements in the 591 underlying :class:`.Row`. 592 593 """ 594 return ROMappingView(self, [(key, self[key]) for key in self.keys()]) 595 596 def keys(self): 597 """Return a view of 'keys' for string column names represented 598 by the underlying :class:`.Row`. 599 600 """ 601 602 return self._parent.keys 603 604 def values(self): 605 """Return a view of values for the values represented in the 606 underlying :class:`.Row`. 607 608 """ 609 return ROMappingView(self, self._values_impl()) 610