123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- # engine/row.py
- # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- """Define row constructs including :class:`.Row`."""
- import operator
- from .. import util
- from ..sql import util as sql_util
- from ..util.compat import collections_abc
- MD_INDEX = 0 # integer index in cursor.description
- # This reconstructor is necessary so that pickles with the C extension or
- # without use the same Binary format.
- try:
- # We need a different reconstructor on the C extension so that we can
- # add extra checks that fields have correctly been initialized by
- # __setstate__.
- from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor
- # The extra function embedding is needed so that the
- # reconstructor function has the same signature whether or not
- # the extension is present.
- def rowproxy_reconstructor(cls, state):
- return safe_rowproxy_reconstructor(cls, state)
- except ImportError:
- def rowproxy_reconstructor(cls, state):
- obj = cls.__new__(cls)
- obj.__setstate__(state)
- return obj
- KEY_INTEGER_ONLY = 0
- """__getitem__ only allows integer values, raises TypeError otherwise"""
- KEY_OBJECTS_ONLY = 1
- """__getitem__ only allows string/object values, raises TypeError otherwise"""
- KEY_OBJECTS_BUT_WARN = 2
- """__getitem__ allows integer or string/object values, but emits a 2.0
- deprecation warning if string/object is passed"""
- KEY_OBJECTS_NO_WARN = 3
- """__getitem__ allows integer or string/object values with no warnings
- or errors."""
- try:
- from sqlalchemy.cresultproxy import BaseRow
- _baserow_usecext = True
- except ImportError:
- _baserow_usecext = False
- class BaseRow(object):
- __slots__ = ("_parent", "_data", "_keymap", "_key_style")
- def __init__(self, parent, processors, keymap, key_style, data):
- """Row objects are constructed by CursorResult objects."""
- object.__setattr__(self, "_parent", parent)
- if processors:
- object.__setattr__(
- self,
- "_data",
- tuple(
- [
- proc(value) if proc else value
- for proc, value in zip(processors, data)
- ]
- ),
- )
- else:
- object.__setattr__(self, "_data", tuple(data))
- object.__setattr__(self, "_keymap", keymap)
- object.__setattr__(self, "_key_style", key_style)
- def __reduce__(self):
- return (
- rowproxy_reconstructor,
- (self.__class__, self.__getstate__()),
- )
- def _filter_on_values(self, filters):
- return Row(
- self._parent,
- filters,
- self._keymap,
- self._key_style,
- self._data,
- )
- def _values_impl(self):
- return list(self)
- def __iter__(self):
- return iter(self._data)
- def __len__(self):
- return len(self._data)
- def __hash__(self):
- return hash(self._data)
- def _get_by_int_impl(self, key):
- return self._data[key]
- def _get_by_key_impl(self, key):
- if int in key.__class__.__mro__:
- return self._data[key]
- if self._key_style == KEY_INTEGER_ONLY:
- self._parent._raise_for_nonint(key)
- # the following is all LegacyRow support. none of this
- # should be called if not LegacyRow
- # assert isinstance(self, LegacyRow)
- try:
- rec = self._keymap[key]
- except KeyError as ke:
- rec = self._parent._key_fallback(key, ke)
- except TypeError:
- if isinstance(key, slice):
- return tuple(self._data[key])
- else:
- raise
- mdindex = rec[MD_INDEX]
- if mdindex is None:
- self._parent._raise_for_ambiguous_column_name(rec)
- elif self._key_style == KEY_OBJECTS_BUT_WARN and mdindex != key:
- self._parent._warn_for_nonint(key)
- return self._data[mdindex]
- # The original 1.4 plan was that Row would not allow row["str"]
- # access, however as the C extensions were inadvertently allowing
- # this coupled with the fact that orm Session sets future=True,
- # this allows a softer upgrade path. see #6218
- __getitem__ = _get_by_key_impl
- def _get_by_key_impl_mapping(self, key):
- try:
- rec = self._keymap[key]
- except KeyError as ke:
- rec = self._parent._key_fallback(key, ke)
- mdindex = rec[MD_INDEX]
- if mdindex is None:
- self._parent._raise_for_ambiguous_column_name(rec)
- elif (
- self._key_style == KEY_OBJECTS_ONLY
- and int in key.__class__.__mro__
- ):
- raise KeyError(key)
- return self._data[mdindex]
- def __getattr__(self, name):
- try:
- return self._get_by_key_impl_mapping(name)
- except KeyError as e:
- util.raise_(AttributeError(e.args[0]), replace_context=e)
- class Row(BaseRow, collections_abc.Sequence):
- """Represent a single result row.
- The :class:`.Row` object represents a row of a database result. It is
- typically associated in the 1.x series of SQLAlchemy with the
- :class:`_engine.CursorResult` object, however is also used by the ORM for
- tuple-like results as of SQLAlchemy 1.4.
- The :class:`.Row` object seeks to act as much like a Python named
- tuple as possible. For mapping (i.e. dictionary) behavior on a row,
- such as testing for containment of keys, refer to the :attr:`.Row._mapping`
- attribute.
- .. seealso::
- :ref:`coretutorial_selecting` - includes examples of selecting
- rows from SELECT statements.
- :class:`.LegacyRow` - Compatibility interface introduced in SQLAlchemy
- 1.4.
- .. versionchanged:: 1.4
- Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a
- "proxy" object in that it contains the final form of data within it,
- and now acts mostly like a named tuple. Mapping-like functionality is
- moved to the :attr:`.Row._mapping` attribute, but will remain available
- in SQLAlchemy 1.x series via the :class:`.LegacyRow` class that is used
- by :class:`_engine.LegacyCursorResult`.
- See :ref:`change_4710_core` for background
- on this change.
- """
- __slots__ = ()
- # in 2.0, this should be KEY_INTEGER_ONLY
- _default_key_style = KEY_OBJECTS_BUT_WARN
- def __setattr__(self, name, value):
- raise AttributeError("can't set attribute")
- def __delattr__(self, name):
- raise AttributeError("can't delete attribute")
- @property
- def _mapping(self):
- """Return a :class:`.RowMapping` for this :class:`.Row`.
- This object provides a consistent Python mapping (i.e. dictionary)
- interface for the data contained within the row. The :class:`.Row`
- by itself behaves like a named tuple, however in the 1.4 series of
- SQLAlchemy, the :class:`.LegacyRow` class is still used by Core which
- continues to have mapping-like behaviors against the row object
- itself.
- .. seealso::
- :attr:`.Row._fields`
- .. versionadded:: 1.4
- """
- return RowMapping(
- self._parent,
- None,
- self._keymap,
- RowMapping._default_key_style,
- self._data,
- )
- def _special_name_accessor(name):
- """Handle ambiguous names such as "count" and "index" """
- @property
- def go(self):
- if self._parent._has_key(name):
- return self.__getattr__(name)
- else:
- def meth(*arg, **kw):
- return getattr(collections_abc.Sequence, name)(
- self, *arg, **kw
- )
- return meth
- return go
- count = _special_name_accessor("count")
- index = _special_name_accessor("index")
- def __contains__(self, key):
- return key in self._data
- def __getstate__(self):
- return {
- "_parent": self._parent,
- "_data": self._data,
- "_key_style": self._key_style,
- }
- def __setstate__(self, state):
- parent = state["_parent"]
- object.__setattr__(self, "_parent", parent)
- object.__setattr__(self, "_data", state["_data"])
- object.__setattr__(self, "_keymap", parent._keymap)
- object.__setattr__(self, "_key_style", state["_key_style"])
- def _op(self, other, op):
- return (
- op(tuple(self), tuple(other))
- if isinstance(other, Row)
- else op(tuple(self), other)
- )
- __hash__ = BaseRow.__hash__
- def __lt__(self, other):
- return self._op(other, operator.lt)
- def __le__(self, other):
- return self._op(other, operator.le)
- def __ge__(self, other):
- return self._op(other, operator.ge)
- def __gt__(self, other):
- return self._op(other, operator.gt)
- def __eq__(self, other):
- return self._op(other, operator.eq)
- def __ne__(self, other):
- return self._op(other, operator.ne)
- def __repr__(self):
- return repr(sql_util._repr_row(self))
- @util.deprecated_20(
- ":meth:`.Row.keys`",
- alternative="Use the namedtuple standard accessor "
- ":attr:`.Row._fields`, or for full mapping behavior use "
- "row._mapping.keys() ",
- )
- def keys(self):
- """Return the list of keys as strings represented by this
- :class:`.Row`.
- The keys can represent the labels of the columns returned by a core
- statement or the names of the orm classes returned by an orm
- execution.
- This method is analogous to the Python dictionary ``.keys()`` method,
- except that it returns a list, not an iterator.
- .. seealso::
- :attr:`.Row._fields`
- :attr:`.Row._mapping`
- """
- return self._parent.keys
- @property
- def _fields(self):
- """Return a tuple of string keys as represented by this
- :class:`.Row`.
- The keys can represent the labels of the columns returned by a core
- statement or the names of the orm classes returned by an orm
- execution.
- This attribute is analogous to the Python named tuple ``._fields``
- attribute.
- .. versionadded:: 1.4
- .. seealso::
- :attr:`.Row._mapping`
- """
- return tuple([k for k in self._parent.keys if k is not None])
- def _asdict(self):
- """Return a new dict which maps field names to their corresponding
- values.
- This method is analogous to the Python named tuple ``._asdict()``
- method, and works by applying the ``dict()`` constructor to the
- :attr:`.Row._mapping` attribute.
- .. versionadded:: 1.4
- .. seealso::
- :attr:`.Row._mapping`
- """
- return dict(self._mapping)
- def _replace(self):
- raise NotImplementedError()
- @property
- def _field_defaults(self):
- raise NotImplementedError()
- class LegacyRow(Row):
- """A subclass of :class:`.Row` that delivers 1.x SQLAlchemy behaviors
- for Core.
- The :class:`.LegacyRow` class is where most of the Python mapping
- (i.e. dictionary-like)
- behaviors are implemented for the row object. The mapping behavior
- of :class:`.Row` going forward is accessible via the :class:`.Row._mapping`
- attribute.
- .. versionadded:: 1.4 - added :class:`.LegacyRow` which encapsulates most
- of the deprecated behaviors of :class:`.Row`.
- """
- __slots__ = ()
- if util.SQLALCHEMY_WARN_20:
- _default_key_style = KEY_OBJECTS_BUT_WARN
- else:
- _default_key_style = KEY_OBJECTS_NO_WARN
- def __contains__(self, key):
- return self._parent._contains(key, self)
- # prior to #6218, LegacyRow would redirect the behavior of __getitem__
- # for the non C version of BaseRow. This is now set up by Python BaseRow
- # in all cases
- # if not _baserow_usecext:
- # __getitem__ = BaseRow._get_by_key_impl
- @util.deprecated(
- "1.4",
- "The :meth:`.LegacyRow.has_key` method is deprecated and will be "
- "removed in a future release. To test for key membership, use "
- "the :attr:`Row._mapping` attribute, i.e. 'key in row._mapping`.",
- )
- def has_key(self, key):
- """Return True if this :class:`.LegacyRow` contains the given key.
- Through the SQLAlchemy 1.x series, the ``__contains__()`` method of
- :class:`.Row` (or :class:`.LegacyRow` as of SQLAlchemy 1.4) also links
- to :meth:`.Row.has_key`, in that an expression such as ::
- "some_col" in row
- Will return True if the row contains a column named ``"some_col"``,
- in the way that a Python mapping works.
- However, it is planned that the 2.0 series of SQLAlchemy will reverse
- this behavior so that ``__contains__()`` will refer to a value being
- present in the row, in the way that a Python tuple works.
- .. seealso::
- :ref:`change_4710_core`
- """
- return self._parent._has_key(key)
- @util.deprecated(
- "1.4",
- "The :meth:`.LegacyRow.items` method is deprecated and will be "
- "removed in a future release. Use the :attr:`Row._mapping` "
- "attribute, i.e., 'row._mapping.items()'.",
- )
- def items(self):
- """Return a list of tuples, each tuple containing a key/value pair.
- This method is analogous to the Python dictionary ``.items()`` method,
- except that it returns a list, not an iterator.
- """
- return [(key, self[key]) for key in self.keys()]
- @util.deprecated(
- "1.4",
- "The :meth:`.LegacyRow.iterkeys` method is deprecated and will be "
- "removed in a future release. Use the :attr:`Row._mapping` "
- "attribute, i.e., 'row._mapping.keys()'.",
- )
- def iterkeys(self):
- """Return a an iterator against the :meth:`.Row.keys` method.
- This method is analogous to the Python-2-only dictionary
- ``.iterkeys()`` method.
- """
- return iter(self._parent.keys)
- @util.deprecated(
- "1.4",
- "The :meth:`.LegacyRow.itervalues` method is deprecated and will be "
- "removed in a future release. Use the :attr:`Row._mapping` "
- "attribute, i.e., 'row._mapping.values()'.",
- )
- def itervalues(self):
- """Return a an iterator against the :meth:`.Row.values` method.
- This method is analogous to the Python-2-only dictionary
- ``.itervalues()`` method.
- """
- return iter(self)
- @util.deprecated(
- "1.4",
- "The :meth:`.LegacyRow.values` method is deprecated and will be "
- "removed in a future release. Use the :attr:`Row._mapping` "
- "attribute, i.e., 'row._mapping.values()'.",
- )
- def values(self):
- """Return the values represented by this :class:`.Row` as a list.
- This method is analogous to the Python dictionary ``.values()`` method,
- except that it returns a list, not an iterator.
- """
- return self._values_impl()
- BaseRowProxy = BaseRow
- RowProxy = Row
- class ROMappingView(
- collections_abc.KeysView,
- collections_abc.ValuesView,
- collections_abc.ItemsView,
- ):
- __slots__ = (
- "_mapping",
- "_items",
- )
- def __init__(self, mapping, items):
- self._mapping = mapping
- self._items = items
- def __len__(self):
- return len(self._items)
- def __repr__(self):
- return "{0.__class__.__name__}({0._mapping!r})".format(self)
- def __iter__(self):
- return iter(self._items)
- def __contains__(self, item):
- return item in self._items
- def __eq__(self, other):
- return list(other) == list(self)
- def __ne__(self, other):
- return list(other) != list(self)
- class RowMapping(BaseRow, collections_abc.Mapping):
- """A ``Mapping`` that maps column names and objects to :class:`.Row` values.
- The :class:`.RowMapping` is available from a :class:`.Row` via the
- :attr:`.Row._mapping` attribute, as well as from the iterable interface
- provided by the :class:`.MappingResult` object returned by the
- :meth:`_engine.Result.mappings` method.
- :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to
- the contents of the row. This includes support for testing of
- containment of specific keys (string column names or objects), as well
- as iteration of keys, values, and items::
- for row in result:
- if 'a' in row._mapping:
- print("Column 'a': %s" % row._mapping['a'])
- print("Column b: %s" % row._mapping[table.c.b])
- .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the
- mapping-like access previously provided by a database result row,
- which now seeks to behave mostly like a named tuple.
- """
- __slots__ = ()
- _default_key_style = KEY_OBJECTS_ONLY
- if not _baserow_usecext:
- __getitem__ = BaseRow._get_by_key_impl_mapping
- def _values_impl(self):
- return list(self._data)
- def __iter__(self):
- return (k for k in self._parent.keys if k is not None)
- def __len__(self):
- return len(self._data)
- def __contains__(self, key):
- return self._parent._has_key(key)
- def __repr__(self):
- return repr(dict(self))
- def items(self):
- """Return a view of key/value tuples for the elements in the
- underlying :class:`.Row`.
- """
- return ROMappingView(self, [(key, self[key]) for key in self.keys()])
- def keys(self):
- """Return a view of 'keys' for string column names represented
- by the underlying :class:`.Row`.
- """
- return self._parent.keys
- def values(self):
- """Return a view of values for the values represented in the
- underlying :class:`.Row`.
- """
- return ROMappingView(self, self._values_impl())
|