|
@@ -5,11 +5,30 @@ from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
|
5
|
5
|
from lodel.plugin.hooks import LodelHook
|
6
|
6
|
|
7
|
7
|
class LeQueryError(Exception):
|
8
|
|
- pass
|
|
8
|
+ ##@brief Instanciate a new exceptions handling multiple exceptions
|
|
9
|
+ # @param msg str : Exception message
|
|
10
|
+ # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
|
|
11
|
+ def __init__(self, msg = "Unknow error", exceptions = None):
|
|
12
|
+ self._msg = msg
|
|
13
|
+ self._exceptions = dict() if exceptions is None else exceptions
|
|
14
|
+
|
|
15
|
+ def __repr__(self):
|
|
16
|
+ return self.__str__()
|
|
17
|
+
|
|
18
|
+ def __str__(self):
|
|
19
|
+ msg = self._msg
|
|
20
|
+ for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
|
|
21
|
+ for obj, expt in for_iter:
|
|
22
|
+ msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
|
|
23
|
+ expt_obj = obj,
|
|
24
|
+ expt_name=expt.__class__.__name__,
|
|
25
|
+ expt_msg=str(expt)
|
|
26
|
+ )
|
|
27
|
+ return msg
|
9
|
28
|
|
10
|
29
|
class LeQuery(object):
|
11
|
30
|
|
12
|
|
- ##@brief Hookname preffix
|
|
31
|
+ ##@brief Hookname prefix
|
13
|
32
|
_hook_prefix = None
|
14
|
33
|
##@brief arguments for the LeObject.check_data_value()
|
15
|
34
|
_data_check_args = { 'complete': False, 'allow_internal': False }
|
|
@@ -18,7 +37,7 @@ class LeQuery(object):
|
18
|
37
|
# @param target_class LeObject : class of object the query is about
|
19
|
38
|
def __init__(self, target_class):
|
20
|
39
|
if hook_prefix is None:
|
21
|
|
- raise NotImplementedError("Asbtract class")
|
|
40
|
+ raise NotImplementedError("Abstract class")
|
22
|
41
|
if not issubclass(target_class, LeObject):
|
23
|
42
|
raise TypeError("target class has to be a child class of LeObject")
|
24
|
43
|
self.__target_class = target_class
|
|
@@ -61,29 +80,82 @@ class LeFilteredQuery(LeQuery):
|
61
|
80
|
'!=',
|
62
|
81
|
'<',
|
63
|
82
|
'>',
|
64
|
|
- ' in ',
|
65
|
|
- ' not in ',
|
66
|
|
- ' like ',
|
67
|
|
- ' not like ']
|
|
83
|
+ 'in',
|
|
84
|
+ 'not in',
|
|
85
|
+ 'like',
|
|
86
|
+ 'not like']
|
68
|
87
|
|
69
|
88
|
##@brief Abtract constructor for queries with filter
|
70
|
89
|
# @param target_class LeObject : class of object the query is about
|
71
|
|
- # @param query_filters list : list of string of query filters (or tuple
|
72
|
|
- #(FIELD, OPERATOR, VALUE) )
|
|
90
|
+ # @param query_filters list : with a tuple (only one filter) or a list of tuple
|
|
91
|
+ # or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
|
|
92
|
+ # For tuple (FIELD,OPERATOR,VALUE)
|
73
|
93
|
def __init__(self, target_class, query_filter):
|
74
|
94
|
super().__init__(target_class)
|
75
|
95
|
##@brief The query filter
|
76
|
96
|
self.__query_filter = None
|
77
|
|
- self.set_qeury_filter(query_filter)
|
|
97
|
+ self.set_query_filter(query_filter)
|
78
|
98
|
|
79
|
99
|
##@brief Set the query filter for a query
|
80
|
100
|
def set_query_filter(self, query_filter):
|
81
|
101
|
#
|
82
|
|
- # Query filter check & prepare should be done here
|
83
|
|
- #
|
84
|
|
- self.__query_filter = query_filter
|
|
102
|
+ # Query filter check & prepare
|
|
103
|
+ # query_filters can be a tuple (only one filter), a list of tuple
|
|
104
|
+ # or a dict: {OP,list(filters)} with OP = 'OR' or 'AND
|
|
105
|
+ # For tuple (FIELD,OPERATOR,VALUE)
|
|
106
|
+ # FIELD has to be in the field_names list of target class
|
|
107
|
+ # OPERATOR in query_operator attribute
|
|
108
|
+ # VALUE has to be a correct value for FIELD
|
|
109
|
+
|
|
110
|
+ fieldnames = self.__target_class.fieldnames()
|
|
111
|
+ # Recursive method which checks filters
|
|
112
|
+ def check_tuple(tupl, fieldnames, target_class):
|
|
113
|
+ if isinstance(tupl, tuple):
|
|
114
|
+ if tupl[0] not in fieldnames:
|
|
115
|
+ return False
|
|
116
|
+ if tupl[1] not in self.query_operators:
|
|
117
|
+ return False
|
|
118
|
+ if not isinstance(tupl[2], target_class.datahandler(tupl[0])):
|
|
119
|
+ return False
|
|
120
|
+ return True
|
|
121
|
+ elif isinstance(tupl,dict):
|
|
122
|
+ return check_tuple(tupl[1])
|
|
123
|
+ elif isinstance(tupl,list):
|
|
124
|
+ for tup in tupl:
|
|
125
|
+ return check_tuple(tup)
|
|
126
|
+ else:
|
|
127
|
+ raise TypeError("Wrong filters for query")
|
|
128
|
+
|
|
129
|
+ check_ok=check_tuple(query_filter, fieldnames, self.__target_class)
|
|
130
|
+ if check_ok:
|
|
131
|
+ self.__query_filter = query_filter
|
|
132
|
+
|
|
133
|
+ # try to check if a query string is a simple condition like 'fieldname operator value'
|
|
134
|
+ def simple_cond(cond, target_class):
|
|
135
|
+ # cond is the where clause
|
85
|
136
|
|
86
|
|
-##@brief A query for insert a new object
|
|
137
|
+ if 'AND' in cond.upper():
|
|
138
|
+ return False
|
|
139
|
+ if 'OR' in cond.upper():
|
|
140
|
+ return False
|
|
141
|
+
|
|
142
|
+ n_ops = 0
|
|
143
|
+ for op in target_class.query_operators:
|
|
144
|
+ if op in cond:
|
|
145
|
+ q_op=op
|
|
146
|
+ n_ops+=1
|
|
147
|
+ if n_ops > 1 or n_ops==0:
|
|
148
|
+ raise TypeError("%s isn't a valid sql condition" % cond)
|
|
149
|
+ tupl=cond.partition(q_op)
|
|
150
|
+
|
|
151
|
+ if tupl[0].strip() not in target_class.fieldnames:
|
|
152
|
+ raise TypeError("%s isn't a valid fieldname" % tupl[0].strip())
|
|
153
|
+ if not isinstance(tupl[2].strip(), target_class.datahandler(tupl[0].strip())):
|
|
154
|
+ raise TypeError("%s is not of a valid type for fieldname % s" % tupl[0],tupl[2])
|
|
155
|
+ return True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+##@brief A query to insert a new object
|
87
|
159
|
class LeInsertQuery(LeQuery):
|
88
|
160
|
|
89
|
161
|
_hook_prefix = 'leapi_insert_'
|
|
@@ -92,11 +164,18 @@ class LeInsertQuery(LeQuery):
|
92
|
164
|
def __init__(self, target_class):
|
93
|
165
|
super().__init__(target_class)
|
94
|
166
|
|
95
|
|
- ## @brief Implements an insert query operations
|
|
167
|
+ ## @brief Implements an insert query operation
|
96
|
168
|
# @param **datas : datas to be inserted
|
97
|
169
|
def __query(self, datasource, **datas):
|
98
|
|
- pass
|
|
170
|
+ nb_inserted = datasource.insert(self.__target_class,**datas)
|
|
171
|
+ if nb_inserted < 0:
|
|
172
|
+ raise LeQueryError("Insertion error")
|
|
173
|
+ return nb_inserted
|
99
|
174
|
|
|
175
|
+ ## @brief Execute the insert query
|
|
176
|
+ def execute(self, datasource):
|
|
177
|
+ super().execute()
|
|
178
|
+
|
100
|
179
|
##@brief A query to update datas for a given object
|
101
|
180
|
class LeUpdateQuery(LeFilteredQuery):
|
102
|
181
|
|
|
@@ -108,8 +187,20 @@ class LeUpdateQuery(LeFilteredQuery):
|
108
|
187
|
|
109
|
188
|
##@brief Implements an update query
|
110
|
189
|
# @param **datas : datas to update
|
|
190
|
+ # @returns the number of updated items
|
|
191
|
+ # @exception when the number of updated items is not as expected
|
111
|
192
|
def __query(self, datasource, **datas):
|
112
|
|
- pass
|
|
193
|
+ # select _uid corresponding to query_filter
|
|
194
|
+ l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
|
|
195
|
+ # list of dict l_uids : _uid(s) of the objects to be updated, corresponding datas
|
|
196
|
+ nb_updated = datasource.update(self.__target_class,l_uids, **datas)
|
|
197
|
+ if (nb_updated != len(l_uids):
|
|
198
|
+ raise LeQueryError("Number of updated items: %d is not as expected: %d " % (nb_updated, len(l_uids)))
|
|
199
|
+ return nb_updated
|
|
200
|
+
|
|
201
|
+ ## @brief Execute the update query
|
|
202
|
+ def execute(self, datasource):
|
|
203
|
+ super().execute()
|
113
|
204
|
|
114
|
205
|
##@brief A query to delete an object
|
115
|
206
|
class LeDeleteQuery(LeFilteredQuery):
|
|
@@ -124,8 +215,16 @@ class LeDeleteQuery(LeFilteredQuery):
|
124
|
215
|
super().execute()
|
125
|
216
|
|
126
|
217
|
##@brief Implements delete query operations
|
|
218
|
+ # @returns the number of deleted items
|
|
219
|
+ # @exception when the number of deleted items is not as expected
|
127
|
220
|
def __query(self, datasource):
|
128
|
|
- pass
|
|
221
|
+ # select _uid corresponding to query_filter
|
|
222
|
+ l_uids=datasource.select(self.__target_class,list(self.__target_class.getuid()),query_filter,None, None, None, None, 0, False)
|
|
223
|
+ # list of dict l_uids : _uid(s) of the objects to be deleted
|
|
224
|
+ nb_deleted = datasource.update(self.__target_class,l_uids, **datas)
|
|
225
|
+ if (nb_deleted != len(l_uids):
|
|
226
|
+ raise LeQueryError("Number of deleted items %d is not as expected %d " % (nb_deleted, len(l_uids)))
|
|
227
|
+ return nb_deleted
|
129
|
228
|
|
130
|
229
|
class LeGetQuery(LeFilteredQuery):
|
131
|
230
|
|
|
@@ -133,8 +232,8 @@ class LeGetQuery(LeFilteredQuery):
|
133
|
232
|
|
134
|
233
|
##@brief Instanciate a new get query
|
135
|
234
|
# @param target_class LeObject : class of object the query is about
|
136
|
|
- # @param query_filters list : list of string of query filters (or tuple
|
137
|
|
- #(FIELD, OPERATOR, VALUE) )
|
|
235
|
+ # @param query_filters dict : {OP, list of query filters }
|
|
236
|
+ # or tuple (FIELD, OPERATOR, VALUE) )
|
138
|
237
|
# @param field_list list|None : list of string representing fields see @ref leobject_filters
|
139
|
238
|
# @param order list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
|
140
|
239
|
# @param group list : A list of field names or tuple (FIELDNAME, [ASC | DESC])
|
|
@@ -161,7 +260,7 @@ class LeGetQuery(LeFilteredQuery):
|
161
|
260
|
|
162
|
261
|
if 'field_list' not in kwargs:
|
163
|
262
|
#field_list = target_class.get_field_list
|
164
|
|
- pass
|
|
263
|
+ field_list = target_class.fieldnames()
|
165
|
264
|
else:
|
166
|
265
|
#target_class.check_fields(kwargs['field_list'])
|
167
|
266
|
field_list = kwargs['field_list']
|
|
@@ -190,3 +289,13 @@ class LeGetQuery(LeFilteredQuery):
|
190
|
289
|
def execute(self, datasource):
|
191
|
290
|
super().execute(datasource)
|
192
|
291
|
|
|
292
|
+ ##@brief Implements select query operations
|
|
293
|
+ # @returns a list containing the item(s)
|
|
294
|
+
|
|
295
|
+ def __query(self, datasource):
|
|
296
|
+ # select _uid corresponding to query_filter
|
|
297
|
+ l_datas=datasource.select(self.__target_class,list(self.field_list),self.query_filter,None, self.__order, self.__group, self.__limit, self.offset, False)
|
|
298
|
+ return l_datas
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|