##// END OF EJS Templates
fix(deprecations): fixed sqlalchemy warning about non-cachable JsonObject
super-admin -
r5223:6c71ac19 default
parent child Browse files
Show More
@@ -1,181 +1,182 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import sqlalchemy
21 21 from sqlalchemy import UnicodeText
22 22 from sqlalchemy.ext.mutable import Mutable, MutableList, MutableDict
23 23
24 24 from rhodecode.lib import ext_json
25 25
26 26
27 27 class JsonRaw(str):
28 28 """
29 29 Allows interacting with a JSON types field using a raw string.
30 30
31 31 For example:
32 32 db_instance = JsonTable()
33 33 db_instance.enabled = True
34 34 db_instance.json_data = JsonRaw('{"a": 4}')
35 35
36 36 This will bypass serialization/checks, and allow storing
37 37 raw values
38 38 """
39 39 pass
40 40
41 41
42 42 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
43 43 """
44 44 Represents an immutable structure as a json-encoded string.
45 45
46 46 If default is, for example, a dict, then a NULL value in the
47 47 database will be exposed as an empty dict.
48 48 """
49 49
50 50 impl = UnicodeText
51 51 safe = True
52 52 enforce_str = True
53 cache_ok = True
53 54
54 55 def __init__(self, *args, **kwargs):
55 56 self.default = kwargs.pop('default', None)
56 57 self.safe = kwargs.pop('safe_json', self.safe)
57 58 self.enforce_str = kwargs.pop('enforce_str', self.enforce_str)
58 59 self.dialect_map = kwargs.pop('dialect_map', {})
59 60 super(JSONEncodedObj, self).__init__(*args, **kwargs)
60 61
61 62 def load_dialect_impl(self, dialect):
62 63 if dialect.name in self.dialect_map:
63 64 return dialect.type_descriptor(self.dialect_map[dialect.name])
64 65 return dialect.type_descriptor(self.impl)
65 66
66 67 def process_bind_param(self, value, dialect):
67 68 if isinstance(value, JsonRaw):
68 69 value = value
69 70 elif value is not None:
70 71 if self.enforce_str:
71 72 value = ext_json.str_json(value)
72 73 else:
73 74 value = ext_json.json.dumps(value)
74 75 return value
75 76
76 77 def process_result_value(self, value, dialect):
77 78 if self.default is not None and (not value or value == '""'):
78 79 return self.default()
79 80
80 81 if value is not None:
81 82 try:
82 83 value = ext_json.json.loads(value)
83 84 except Exception:
84 85 if self.safe and self.default is not None:
85 86 return self.default()
86 87 else:
87 88 raise
88 89 return value
89 90
90 91
91 92 class MutationObj(Mutable):
92 93
93 94 @classmethod
94 95 def coerce(cls, key, value):
95 96 if isinstance(value, dict) and not isinstance(value, MutationDict):
96 97 return MutationDict.coerce(key, value)
97 98 if isinstance(value, list) and not isinstance(value, MutationList):
98 99 return MutationList.coerce(key, value)
99 100 return value
100 101
101 102 def de_coerce(self) -> "MutationObj":
102 103 return self
103 104
104 105 @classmethod
105 106 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
106 107 key = attribute.key
107 108 if parent_cls is not attribute.class_:
108 109 return
109 110
110 111 # rely on "propagate" here
111 112 parent_cls = attribute.class_
112 113
113 114 def load(state, *args):
114 115 val = state.dict.get(key, None)
115 116 if coerce:
116 117 val = cls.coerce(key, val)
117 118 state.dict[key] = val
118 119 if isinstance(val, cls):
119 120 val._parents[state.obj()] = key
120 121
121 122 def set(target, value, oldvalue, initiator):
122 123 if not isinstance(value, cls):
123 124 value = cls.coerce(key, value)
124 125 if isinstance(value, cls):
125 126 value._parents[target.obj()] = key
126 127 if isinstance(oldvalue, cls):
127 128 oldvalue._parents.pop(target.obj(), None)
128 129 return value
129 130
130 131 def pickle(state, state_dict):
131 132 val = state.dict.get(key, None)
132 133 if isinstance(val, cls):
133 134 if 'ext.mutable.values' not in state_dict:
134 135 state_dict['ext.mutable.values'] = []
135 136 state_dict['ext.mutable.values'].append(val)
136 137
137 138 def unpickle(state, state_dict):
138 139 if 'ext.mutable.values' in state_dict:
139 140 for val in state_dict['ext.mutable.values']:
140 141 val._parents[state.obj()] = key
141 142
142 143 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
143 144 propagate=True)
144 145 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
145 146 propagate=True)
146 147 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
147 148 propagate=True)
148 149 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
149 150 propagate=True)
150 151 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
151 152 propagate=True)
152 153
153 154
154 155 class MutationList(MutableList):
155 156 def de_coerce(self):
156 157 return list(self)
157 158
158 159
159 160 class MutationDict(MutableDict):
160 161 def de_coerce(self):
161 162 return dict(self)
162 163
163 164
164 165 def JsonType(impl=None, **kwargs):
165 166 """
166 167 Helper for using a mutation obj, it allows to use .with_variant easily.
167 168 example::
168 169
169 170 settings = Column('settings_json',
170 171 MutationObj.as_mutable(
171 172 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
172 173 """
173 174
174 175 if impl == 'list':
175 176 return JSONEncodedObj(default=list, **kwargs)
176 177 elif impl == 'dict':
177 178 return JSONEncodedObj(default=dict, **kwargs)
178 179 else:
179 180 return JSONEncodedObj(**kwargs)
180 181
181 182
General Comments 0
You need to be logged in to leave comments. Login now