##// END OF EJS Templates
db: enforce unicode on unicode columns to prevent warnings.
marcink -
r3948:e0de097c default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,274 +1,279 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22
22
23 import sqlalchemy
23 import sqlalchemy
24 from sqlalchemy import UnicodeText
24 from sqlalchemy import UnicodeText
25 from sqlalchemy.ext.mutable import Mutable
25 from sqlalchemy.ext.mutable import Mutable
26
26
27 from rhodecode.lib.ext_json import json
27 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.utils2 import safe_unicode
28
29
29
30
30 class JsonRaw(unicode):
31 class JsonRaw(unicode):
31 """
32 """
32 Allows interacting with a JSON types field using a raw string.
33 Allows interacting with a JSON types field using a raw string.
33
34
34 For example::
35 For example::
35 db_instance = JsonTable()
36 db_instance = JsonTable()
36 db_instance.enabled = True
37 db_instance.enabled = True
37 db_instance.json_data = JsonRaw('{"a": 4}')
38 db_instance.json_data = JsonRaw('{"a": 4}')
38
39
39 This will bypass serialization/checks, and allow storing
40 This will bypass serialization/checks, and allow storing
40 raw values
41 raw values
41 """
42 """
42 pass
43 pass
43
44
44
45
45 # Set this to the standard dict if Order is not required
46 # Set this to the standard dict if Order is not required
46 DictClass = collections.OrderedDict
47 DictClass = collections.OrderedDict
47
48
48
49
49 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
50 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
50 """
51 """
51 Represents an immutable structure as a json-encoded string.
52 Represents an immutable structure as a json-encoded string.
52
53
53 If default is, for example, a dict, then a NULL value in the
54 If default is, for example, a dict, then a NULL value in the
54 database will be exposed as an empty dict.
55 database will be exposed as an empty dict.
55 """
56 """
56
57
57 impl = UnicodeText
58 impl = UnicodeText
58 safe = True
59 safe = True
60 enforce_unicode = True
59
61
60 def __init__(self, *args, **kwargs):
62 def __init__(self, *args, **kwargs):
61 self.default = kwargs.pop('default', None)
63 self.default = kwargs.pop('default', None)
62 self.safe = kwargs.pop('safe_json', self.safe)
64 self.safe = kwargs.pop('safe_json', self.safe)
65 self.enforce_unicode = kwargs.pop('enforce_unicode', self.enforce_unicode)
63 self.dialect_map = kwargs.pop('dialect_map', {})
66 self.dialect_map = kwargs.pop('dialect_map', {})
64 super(JSONEncodedObj, self).__init__(*args, **kwargs)
67 super(JSONEncodedObj, self).__init__(*args, **kwargs)
65
68
66 def load_dialect_impl(self, dialect):
69 def load_dialect_impl(self, dialect):
67 if dialect.name in self.dialect_map:
70 if dialect.name in self.dialect_map:
68 return dialect.type_descriptor(self.dialect_map[dialect.name])
71 return dialect.type_descriptor(self.dialect_map[dialect.name])
69 return dialect.type_descriptor(self.impl)
72 return dialect.type_descriptor(self.impl)
70
73
71 def process_bind_param(self, value, dialect):
74 def process_bind_param(self, value, dialect):
72 if isinstance(value, JsonRaw):
75 if isinstance(value, JsonRaw):
73 value = value
76 value = value
74 elif value is not None:
77 elif value is not None:
75 value = json.dumps(value)
78 value = json.dumps(value)
79 if self.enforce_unicode:
80 value = safe_unicode(value)
76 return value
81 return value
77
82
78 def process_result_value(self, value, dialect):
83 def process_result_value(self, value, dialect):
79 if self.default is not None and (not value or value == '""'):
84 if self.default is not None and (not value or value == '""'):
80 return self.default()
85 return self.default()
81
86
82 if value is not None:
87 if value is not None:
83 try:
88 try:
84 value = json.loads(value, object_pairs_hook=DictClass)
89 value = json.loads(value, object_pairs_hook=DictClass)
85 except Exception as e:
90 except Exception as e:
86 if self.safe and self.default is not None:
91 if self.safe and self.default is not None:
87 return self.default()
92 return self.default()
88 else:
93 else:
89 raise
94 raise
90 return value
95 return value
91
96
92
97
93 class MutationObj(Mutable):
98 class MutationObj(Mutable):
94 @classmethod
99 @classmethod
95 def coerce(cls, key, value):
100 def coerce(cls, key, value):
96 if isinstance(value, dict) and not isinstance(value, MutationDict):
101 if isinstance(value, dict) and not isinstance(value, MutationDict):
97 return MutationDict.coerce(key, value)
102 return MutationDict.coerce(key, value)
98 if isinstance(value, list) and not isinstance(value, MutationList):
103 if isinstance(value, list) and not isinstance(value, MutationList):
99 return MutationList.coerce(key, value)
104 return MutationList.coerce(key, value)
100 return value
105 return value
101
106
102 def de_coerce(self):
107 def de_coerce(self):
103 return self
108 return self
104
109
105 @classmethod
110 @classmethod
106 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
111 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
107 key = attribute.key
112 key = attribute.key
108 if parent_cls is not attribute.class_:
113 if parent_cls is not attribute.class_:
109 return
114 return
110
115
111 # rely on "propagate" here
116 # rely on "propagate" here
112 parent_cls = attribute.class_
117 parent_cls = attribute.class_
113
118
114 def load(state, *args):
119 def load(state, *args):
115 val = state.dict.get(key, None)
120 val = state.dict.get(key, None)
116 if coerce:
121 if coerce:
117 val = cls.coerce(key, val)
122 val = cls.coerce(key, val)
118 state.dict[key] = val
123 state.dict[key] = val
119 if isinstance(val, cls):
124 if isinstance(val, cls):
120 val._parents[state.obj()] = key
125 val._parents[state.obj()] = key
121
126
122 def set(target, value, oldvalue, initiator):
127 def set(target, value, oldvalue, initiator):
123 if not isinstance(value, cls):
128 if not isinstance(value, cls):
124 value = cls.coerce(key, value)
129 value = cls.coerce(key, value)
125 if isinstance(value, cls):
130 if isinstance(value, cls):
126 value._parents[target.obj()] = key
131 value._parents[target.obj()] = key
127 if isinstance(oldvalue, cls):
132 if isinstance(oldvalue, cls):
128 oldvalue._parents.pop(target.obj(), None)
133 oldvalue._parents.pop(target.obj(), None)
129 return value
134 return value
130
135
131 def pickle(state, state_dict):
136 def pickle(state, state_dict):
132 val = state.dict.get(key, None)
137 val = state.dict.get(key, None)
133 if isinstance(val, cls):
138 if isinstance(val, cls):
134 if 'ext.mutable.values' not in state_dict:
139 if 'ext.mutable.values' not in state_dict:
135 state_dict['ext.mutable.values'] = []
140 state_dict['ext.mutable.values'] = []
136 state_dict['ext.mutable.values'].append(val)
141 state_dict['ext.mutable.values'].append(val)
137
142
138 def unpickle(state, state_dict):
143 def unpickle(state, state_dict):
139 if 'ext.mutable.values' in state_dict:
144 if 'ext.mutable.values' in state_dict:
140 for val in state_dict['ext.mutable.values']:
145 for val in state_dict['ext.mutable.values']:
141 val._parents[state.obj()] = key
146 val._parents[state.obj()] = key
142
147
143 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
148 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
144 propagate=True)
149 propagate=True)
145 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
150 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
146 propagate=True)
151 propagate=True)
147 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
152 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
148 propagate=True)
153 propagate=True)
149 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
154 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
150 propagate=True)
155 propagate=True)
151 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
156 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
152 propagate=True)
157 propagate=True)
153
158
154
159
155 class MutationDict(MutationObj, DictClass):
160 class MutationDict(MutationObj, DictClass):
156 @classmethod
161 @classmethod
157 def coerce(cls, key, value):
162 def coerce(cls, key, value):
158 """Convert plain dictionary to MutationDict"""
163 """Convert plain dictionary to MutationDict"""
159 self = MutationDict(
164 self = MutationDict(
160 (k, MutationObj.coerce(key, v)) for (k, v) in value.items())
165 (k, MutationObj.coerce(key, v)) for (k, v) in value.items())
161 self._key = key
166 self._key = key
162 return self
167 return self
163
168
164 def de_coerce(self):
169 def de_coerce(self):
165 return dict(self)
170 return dict(self)
166
171
167 def __setitem__(self, key, value):
172 def __setitem__(self, key, value):
168 # Due to the way OrderedDict works, this is called during __init__.
173 # Due to the way OrderedDict works, this is called during __init__.
169 # At this time we don't have a key set, but what is more, the value
174 # At this time we don't have a key set, but what is more, the value
170 # being set has already been coerced. So special case this and skip.
175 # being set has already been coerced. So special case this and skip.
171 if hasattr(self, '_key'):
176 if hasattr(self, '_key'):
172 value = MutationObj.coerce(self._key, value)
177 value = MutationObj.coerce(self._key, value)
173 DictClass.__setitem__(self, key, value)
178 DictClass.__setitem__(self, key, value)
174 self.changed()
179 self.changed()
175
180
176 def __delitem__(self, key):
181 def __delitem__(self, key):
177 DictClass.__delitem__(self, key)
182 DictClass.__delitem__(self, key)
178 self.changed()
183 self.changed()
179
184
180 def __setstate__(self, state):
185 def __setstate__(self, state):
181 self.__dict__ = state
186 self.__dict__ = state
182
187
183 def __reduce_ex__(self, proto):
188 def __reduce_ex__(self, proto):
184 # support pickling of MutationDicts
189 # support pickling of MutationDicts
185 d = dict(self)
190 d = dict(self)
186 return (self.__class__, (d,))
191 return (self.__class__, (d,))
187
192
188
193
189 class MutationList(MutationObj, list):
194 class MutationList(MutationObj, list):
190 @classmethod
195 @classmethod
191 def coerce(cls, key, value):
196 def coerce(cls, key, value):
192 """Convert plain list to MutationList"""
197 """Convert plain list to MutationList"""
193 self = MutationList((MutationObj.coerce(key, v) for v in value))
198 self = MutationList((MutationObj.coerce(key, v) for v in value))
194 self._key = key
199 self._key = key
195 return self
200 return self
196
201
197 def de_coerce(self):
202 def de_coerce(self):
198 return list(self)
203 return list(self)
199
204
200 def __setitem__(self, idx, value):
205 def __setitem__(self, idx, value):
201 list.__setitem__(self, idx, MutationObj.coerce(self._key, value))
206 list.__setitem__(self, idx, MutationObj.coerce(self._key, value))
202 self.changed()
207 self.changed()
203
208
204 def __setslice__(self, start, stop, values):
209 def __setslice__(self, start, stop, values):
205 list.__setslice__(self, start, stop,
210 list.__setslice__(self, start, stop,
206 (MutationObj.coerce(self._key, v) for v in values))
211 (MutationObj.coerce(self._key, v) for v in values))
207 self.changed()
212 self.changed()
208
213
209 def __delitem__(self, idx):
214 def __delitem__(self, idx):
210 list.__delitem__(self, idx)
215 list.__delitem__(self, idx)
211 self.changed()
216 self.changed()
212
217
213 def __delslice__(self, start, stop):
218 def __delslice__(self, start, stop):
214 list.__delslice__(self, start, stop)
219 list.__delslice__(self, start, stop)
215 self.changed()
220 self.changed()
216
221
217 def append(self, value):
222 def append(self, value):
218 list.append(self, MutationObj.coerce(self._key, value))
223 list.append(self, MutationObj.coerce(self._key, value))
219 self.changed()
224 self.changed()
220
225
221 def insert(self, idx, value):
226 def insert(self, idx, value):
222 list.insert(self, idx, MutationObj.coerce(self._key, value))
227 list.insert(self, idx, MutationObj.coerce(self._key, value))
223 self.changed()
228 self.changed()
224
229
225 def extend(self, values):
230 def extend(self, values):
226 list.extend(self, (MutationObj.coerce(self._key, v) for v in values))
231 list.extend(self, (MutationObj.coerce(self._key, v) for v in values))
227 self.changed()
232 self.changed()
228
233
229 def pop(self, *args, **kw):
234 def pop(self, *args, **kw):
230 value = list.pop(self, *args, **kw)
235 value = list.pop(self, *args, **kw)
231 self.changed()
236 self.changed()
232 return value
237 return value
233
238
234 def remove(self, value):
239 def remove(self, value):
235 list.remove(self, value)
240 list.remove(self, value)
236 self.changed()
241 self.changed()
237
242
238
243
239 def JsonType(impl=None, **kwargs):
244 def JsonType(impl=None, **kwargs):
240 """
245 """
241 Helper for using a mutation obj, it allows to use .with_variant easily.
246 Helper for using a mutation obj, it allows to use .with_variant easily.
242 example::
247 example::
243
248
244 settings = Column('settings_json',
249 settings = Column('settings_json',
245 MutationObj.as_mutable(
250 MutationObj.as_mutable(
246 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
251 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
247 """
252 """
248
253
249 if impl == 'list':
254 if impl == 'list':
250 return JSONEncodedObj(default=list, **kwargs)
255 return JSONEncodedObj(default=list, **kwargs)
251 elif impl == 'dict':
256 elif impl == 'dict':
252 return JSONEncodedObj(default=DictClass, **kwargs)
257 return JSONEncodedObj(default=DictClass, **kwargs)
253 else:
258 else:
254 return JSONEncodedObj(**kwargs)
259 return JSONEncodedObj(**kwargs)
255
260
256
261
257 JSON = MutationObj.as_mutable(JsonType())
262 JSON = MutationObj.as_mutable(JsonType())
258 """
263 """
259 A type to encode/decode JSON on the fly
264 A type to encode/decode JSON on the fly
260
265
261 sqltype is the string type for the underlying DB column::
266 sqltype is the string type for the underlying DB column::
262
267
263 Column(JSON) (defaults to UnicodeText)
268 Column(JSON) (defaults to UnicodeText)
264 """
269 """
265
270
266 JSONDict = MutationObj.as_mutable(JsonType('dict'))
271 JSONDict = MutationObj.as_mutable(JsonType('dict'))
267 """
272 """
268 A type to encode/decode JSON dictionaries on the fly
273 A type to encode/decode JSON dictionaries on the fly
269 """
274 """
270
275
271 JSONList = MutationObj.as_mutable(JsonType('list'))
276 JSONList = MutationObj.as_mutable(JsonType('list'))
272 """
277 """
273 A type to encode/decode JSON lists` on the fly
278 A type to encode/decode JSON lists` on the fly
274 """
279 """
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now