summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/engine/_py_row.py
blob: 981b6e0b2772ed8304cb2274ad7d75a9846b442a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import operator

MD_INDEX = 0  # integer index in cursor.description

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"""

sqlalchemy_engine_row = None


class BaseRow:
    Row = None
    __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 __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 _filter_on_values(self, filters):
        global sqlalchemy_engine_row
        if sqlalchemy_engine_row is None:
            from sqlalchemy.engine.row import Row as sqlalchemy_engine_row

        return sqlalchemy_engine_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):
        # keep two isinstance since it's noticeably faster in the int case
        if isinstance(key, int) or isinstance(key, slice):
            return self._data[key]

        self._parent._raise_for_nonint(key)

    # 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 isinstance(key, int):
            raise KeyError(key)

        return self._data[mdindex]

    def __getattr__(self, name):
        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, state):
    obj = cls.__new__(cls)
    obj.__setstate__(state)
    return obj


def tuplegetter(*indexes):
    it = operator.itemgetter(*indexes)

    if len(indexes) > 1:
        return it
    else:
        return lambda row: (it(row),)