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