from __future__ import annotations import enum import operator import typing from typing import Any from typing import Callable from typing import Dict from typing import Iterator from typing import List from typing import Optional from typing import Tuple from typing import Type from typing import Union if typing.TYPE_CHECKING: from .result import _KeyMapType from .result import _KeyType from .result import _ProcessorsType from .result import _RawRowType from .result import _TupleGetterType from .result import ResultMetaData MD_INDEX = 0 # integer index in cursor.description class _KeyStyle(enum.IntEnum): KEY_INTEGER_ONLY = 0 """__getitem__ only allows integer values and slices, raises TypeError otherwise""" KEY_OBJECTS_ONLY = 1 """__getitem__ only allows string/object values, raises TypeError otherwise""" KEY_INTEGER_ONLY, KEY_OBJECTS_ONLY = list(_KeyStyle) class BaseRow: __slots__ = ("_parent", "_data", "_keymap", "_key_style") _parent: ResultMetaData _data: _RawRowType _keymap: _KeyMapType _key_style: _KeyStyle def __init__( self, parent: ResultMetaData, processors: Optional[_ProcessorsType], keymap: _KeyMapType, key_style: _KeyStyle, data: _RawRowType, ): """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) -> Tuple[Callable[..., BaseRow], Tuple[Any, ...]]: return ( rowproxy_reconstructor, (self.__class__, self.__getstate__()), ) def __getstate__(self) -> Dict[str, Any]: return { "_parent": self._parent, "_data": self._data, "_key_style": self._key_style, } def __setstate__(self, state: Dict[str, Any]) -> None: 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 _values_impl(self) -> List[Any]: return list(self) def __iter__(self) -> Iterator[Any]: return iter(self._data) def __len__(self) -> int: return len(self._data) def __hash__(self) -> int: return hash(self._data) def _get_by_int_impl(self, key: Union[int, slice]) -> Any: return self._data[key] if not typing.TYPE_CHECKING: __getitem__ = _get_by_int_impl def _get_by_key_impl_mapping(self, key: _KeyType) -> Any: 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) # NOTE: keep "== KEY_OBJECTS_ONLY" instead of "is KEY_OBJECTS_ONLY" # since deserializing the class from cython will load an int in # _key_style, not an instance of _KeyStyle elif self._key_style == KEY_OBJECTS_ONLY and isinstance(key, int): raise KeyError(key) return self._data[mdindex] def __getattr__(self, name: str) -> Any: try: return self._get_by_key_impl_mapping(name) except KeyError as e: raise AttributeError(e.args[0]) from e # This reconstructor is necessary so that pickles with the Cy extension or # without use the same Binary format. def rowproxy_reconstructor( cls: Type[BaseRow], state: Dict[str, Any] ) -> BaseRow: obj = cls.__new__(cls) obj.__setstate__(state) return obj def tuplegetter(*indexes: int) -> _TupleGetterType: it = operator.itemgetter(*indexes) if len(indexes) > 1: return it else: return lambda row: (it(row),)