|
@@ -8,20 +8,22 @@ import warnings
|
8
|
8
|
from lodel.context import LodelContext
|
9
|
9
|
LodelContext.expose_modules(globals(), {
|
10
|
10
|
'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
|
11
|
|
- 'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
12
|
|
- 'LeApiQueryErrors'],
|
|
11
|
+ 'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
|
|
12
|
+ 'LeApiQueryErrors'],
|
13
|
13
|
'lodel.plugin.hooks': ['LodelHook'],
|
14
|
14
|
'lodel.logger': ['logger']})
|
15
|
15
|
|
16
|
|
-##@todo check datas when running query
|
|
16
|
+# @todo check data when running query
|
|
17
|
+
|
|
18
|
+
|
17
|
19
|
class LeQuery(object):
|
18
|
20
|
|
19
|
|
- ##@brief Hookname prefix
|
|
21
|
+ # @brief Hookname prefix
|
20
|
22
|
_hook_prefix = None
|
21
|
|
- ##@brief arguments for the LeObject.check_data_value()
|
|
23
|
+ # @brief arguments for the LeObject.check_data_value()
|
22
|
24
|
_data_check_args = {'complete': False, 'allow_internal': False}
|
23
|
25
|
|
24
|
|
- ##@brief Abstract constructor
|
|
26
|
+ # @brief Abstract constructor
|
25
|
27
|
# @param target_class LeObject : class of object the query is about
|
26
|
28
|
def __init__(self, target_class):
|
27
|
29
|
from .leobject import LeObject
|
|
@@ -29,77 +31,80 @@ class LeQuery(object):
|
29
|
31
|
raise NotImplementedError("Abstract class")
|
30
|
32
|
if not inspect.isclass(target_class) or \
|
31
|
33
|
not issubclass(target_class, LeObject):
|
32
|
|
- raise TypeError("target class has to be a child class of LeObject but %s given"% target_class)
|
|
34
|
+ raise TypeError(
|
|
35
|
+ "target class has to be a child class of LeObject but %s given" % target_class)
|
33
|
36
|
self._target_class = target_class
|
34
|
37
|
self._ro_datasource = target_class._ro_datasource
|
35
|
38
|
self._rw_datasource = target_class._rw_datasource
|
36
|
39
|
|
37
|
|
- ##@brief Execute a query and return the result
|
38
|
|
- #@param **datas
|
|
40
|
+ # @brief Executes a query and returns the result
|
|
41
|
+ #@param **data
|
39
|
42
|
#@return the query result
|
40
|
43
|
#@see LeQuery._query()
|
41
|
44
|
#@todo check that the check_datas_value is not duplicated/useless
|
42
|
|
- def execute(self, datas):
|
43
|
|
- if not datas is None:
|
|
45
|
+ def execute(self, data):
|
|
46
|
+ if data is not None:
|
44
|
47
|
self._target_class.check_datas_value(
|
45
|
|
- datas,
|
46
|
|
- **self._data_check_args)
|
47
|
|
- self._target_class.prepare_datas(datas) #not yet implemented
|
|
48
|
+ data,
|
|
49
|
+ **self._data_check_args)
|
|
50
|
+ self._target_class.prepare_datas(data) # not yet implemented
|
48
|
51
|
if self._hook_prefix is None:
|
49
|
52
|
raise NotImplementedError("Abstract method")
|
50
|
|
- LodelHook.call_hook(self._hook_prefix+'pre',
|
51
|
|
- self._target_class,
|
52
|
|
- datas)
|
53
|
|
- ret = self._query(datas=datas)
|
54
|
|
- ret = LodelHook.call_hook(self._hook_prefix+'post',
|
55
|
|
- self._target_class,
|
56
|
|
- ret)
|
|
53
|
+ LodelHook.call_hook(self._hook_prefix + 'pre',
|
|
54
|
+ self._target_class,
|
|
55
|
+ data)
|
|
56
|
+ ret = self._query(data=data)
|
|
57
|
+ ret = LodelHook.call_hook(self._hook_prefix + 'post',
|
|
58
|
+ self._target_class,
|
|
59
|
+ ret)
|
57
|
60
|
return ret
|
58
|
61
|
|
59
|
|
- ##@brief Childs classes implements this method to execute the query
|
60
|
|
- #@param **datas
|
|
62
|
+ # @brief Child classes implement this method to execute the query
|
|
63
|
+ #@param **data
|
61
|
64
|
#@return query result
|
62
|
|
- def _query(self, **datas):
|
|
65
|
+ def _query(self, **data):
|
63
|
66
|
raise NotImplementedError("Asbtract method")
|
64
|
67
|
|
65
|
|
- ##@return a dict with query infos
|
|
68
|
+ # @return a dict with query infos
|
66
|
69
|
def dump_infos(self):
|
67
|
70
|
return {'target_class': self._target_class}
|
68
|
71
|
|
69
|
72
|
def __repr__(self):
|
70
|
73
|
ret = "<{classname} target={target_class}>"
|
71
|
74
|
return ret.format(
|
72
|
|
- classname=self.__class__.__name__,
|
73
|
|
- target_class = self._target_class)
|
|
75
|
+ classname=self.__class__.__name__,
|
|
76
|
+ target_class=self._target_class)
|
|
77
|
+
|
|
78
|
+# @brief Abstract class handling query with filters
|
|
79
|
+
|
74
|
80
|
|
75
|
|
-##@brief Abstract class handling query with filters
|
76
|
81
|
class LeFilteredQuery(LeQuery):
|
77
|
|
- ##@brief The available operators used in query definitions
|
|
82
|
+ # @brief The available operators used in query definitions
|
78
|
83
|
_query_operators = [
|
79
|
|
- ' = ',
|
80
|
|
- ' <= ',
|
81
|
|
- ' >= ',
|
82
|
|
- ' != ',
|
83
|
|
- ' < ',
|
84
|
|
- ' > ',
|
85
|
|
- ' in ',
|
86
|
|
- ' not in ',
|
87
|
|
- ' like ',
|
88
|
|
- ' not like ']
|
89
|
|
-
|
90
|
|
- ##@brief Regular expression to process filters
|
|
84
|
+ ' = ',
|
|
85
|
+ ' <= ',
|
|
86
|
+ ' >= ',
|
|
87
|
+ ' != ',
|
|
88
|
+ ' < ',
|
|
89
|
+ ' > ',
|
|
90
|
+ ' in ',
|
|
91
|
+ ' not in ',
|
|
92
|
+ ' like ',
|
|
93
|
+ ' not like ']
|
|
94
|
+
|
|
95
|
+ # @brief Regular expression to process filters
|
91
|
96
|
_query_re = None
|
92
|
97
|
|
93
|
|
- ##@brief Abtract constructor for queries with filter
|
|
98
|
+ # @brief Abtract constructor for queries with filter
|
94
|
99
|
#@param target_class LeObject : class of object the query is about
|
95
|
100
|
#@param query_filters list : with a tuple (only one filter) or a list of
|
96
|
101
|
# tuple or a dict: {OP,list(filters)} with OP = 'OR' or 'AND for tuple
|
97
|
102
|
# (FIELD,OPERATOR,VALUE)
|
98
|
103
|
def __init__(self, target_class, query_filters=None):
|
99
|
104
|
super().__init__(target_class)
|
100
|
|
- ##@brief The query filter tuple(std_filter, relational_filters)
|
|
105
|
+ # @brief The query filter tuple(std_filter, relational_filters)
|
101
|
106
|
self._query_filter = None
|
102
|
|
- ##@brief Stores potential subqueries (used when a query implies
|
|
107
|
+ # @brief Stores potential subqueries (used when a query implies
|
103
|
108
|
# more than one datasource.
|
104
|
109
|
#
|
105
|
110
|
# Subqueries are tuple(target_class_ref_field, LeGetQuery)
|
|
@@ -107,11 +112,11 @@ class LeFilteredQuery(LeQuery):
|
107
|
112
|
query_filters = [] if query_filters is None else query_filters
|
108
|
113
|
self.set_query_filter(query_filters)
|
109
|
114
|
|
110
|
|
- ##@brief Abstract FilteredQuery execution method
|
|
115
|
+ # @brief Abstract FilteredQuery execution method
|
111
|
116
|
#
|
112
|
117
|
# This method takes care to execute subqueries before calling super execute
|
113
|
|
- def execute(self, datas=None):
|
114
|
|
- #copy originals filters
|
|
118
|
+ def execute(self, data=None):
|
|
119
|
+ # copy originals filters
|
115
|
120
|
orig_filters = copy.copy(self._query_filter)
|
116
|
121
|
std_filters, rel_filters = self._query_filter
|
117
|
122
|
|
|
@@ -123,17 +128,17 @@ class LeFilteredQuery(LeQuery):
|
123
|
128
|
try:
|
124
|
129
|
|
125
|
130
|
filters, rel_filters = self._query_filter
|
126
|
|
- res = super().execute(datas)
|
|
131
|
+ res = super().execute(data)
|
127
|
132
|
except Exception as e:
|
128
|
|
- #restoring filters even if an exception is raised
|
|
133
|
+ # restoring filters even if an exception is raised
|
129
|
134
|
self.__query_filter = orig_filters
|
130
|
135
|
|
131
|
|
- raise e #reraise
|
132
|
|
- #restoring filters
|
|
136
|
+ raise e # reraise
|
|
137
|
+ # restoring filters
|
133
|
138
|
self._query_filter = orig_filters
|
134
|
139
|
return res
|
135
|
140
|
|
136
|
|
- ##@brief Add filter(s) to the query
|
|
141
|
+ # @brief Add filter(s) to the query
|
137
|
142
|
#
|
138
|
143
|
# This method is also able to slice query if different datasources are
|
139
|
144
|
# implied in the request
|
|
@@ -144,39 +149,39 @@ class LeFilteredQuery(LeQuery):
|
144
|
149
|
def set_query_filter(self, query_filter):
|
145
|
150
|
if isinstance(query_filter, str):
|
146
|
151
|
query_filter = [query_filter]
|
147
|
|
- #Query filter prepration
|
|
152
|
+ # Query filter prepration
|
148
|
153
|
filters_orig, rel_filters = self._prepare_filters(query_filter)
|
149
|
154
|
# Here we now that each relational filter concern only one datasource
|
150
|
155
|
# thank's to _prepare_relational_fields
|
151
|
156
|
|
152
|
|
- #Multiple datasources detection
|
|
157
|
+ # Multiple datasources detection
|
153
|
158
|
self_ds_name = self._target_class._datasource_name
|
154
|
|
- result_rel_filters = list() # The filters that will stay in the query
|
|
159
|
+ result_rel_filters = list() # The filters that will stay in the query
|
155
|
160
|
other_ds_filters = dict()
|
156
|
161
|
for rfilter in rel_filters:
|
157
|
162
|
(rfield, ref_dict), op, value = rfilter
|
158
|
|
- #rfield : the field in self._target_class
|
159
|
|
- tmp_rel_filter = dict() #designed to stores rel_field of same DS
|
|
163
|
+ # rfield : the field in self._target_class
|
|
164
|
+ tmp_rel_filter = dict() # designed to stores rel_field of same DS
|
160
|
165
|
# First step : simplification
|
161
|
166
|
# Trying to delete relational filters done on referenced class uid
|
162
|
167
|
for tclass, tfield in copy.copy(ref_dict).items():
|
163
|
|
- #tclass : reference target class
|
164
|
|
- #tfield : referenced field from target class
|
|
168
|
+ # tclass : reference target class
|
|
169
|
+ # tfield : referenced field from target class
|
165
|
170
|
#
|
166
|
171
|
# !!!WARNING!!!
|
167
|
172
|
# The line below brake multi UID support
|
168
|
173
|
#
|
169
|
174
|
if tfield == tclass.uid_fieldname()[0]:
|
170
|
|
- #This relational filter can be simplified as
|
|
175
|
+ # This relational filter can be simplified as
|
171
|
176
|
# ref_field, op, value
|
172
|
177
|
# Note : we will have to dedup filters_orig
|
173
|
178
|
filters_orig.append((rfield, op, value))
|
174
|
179
|
del(ref_dict[tclass])
|
175
|
180
|
if len(ref_dict) == 0:
|
176
|
181
|
continue
|
177
|
|
- #Determine what to do with other relational filters given
|
|
182
|
+ # Determine what to do with other relational filters given
|
178
|
183
|
# referenced class datasource
|
179
|
|
- #Remember : each class in a relational filter has the same
|
|
184
|
+ # Remember : each class in a relational filter has the same
|
180
|
185
|
# datasource
|
181
|
186
|
tclass = list(ref_dict.keys())[0]
|
182
|
187
|
cur_ds = tclass._datasource_name
|
|
@@ -189,23 +194,23 @@ class LeFilteredQuery(LeQuery):
|
189
|
194
|
other_ds_filters[cur_ds] = list()
|
190
|
195
|
other_ds_filters[cur_ds].append(
|
191
|
196
|
((rfield, ref_dict), op, value))
|
192
|
|
- #deduplication of std filters
|
|
197
|
+ # deduplication of std filters
|
193
|
198
|
filters_cp = set()
|
194
|
199
|
if not isinstance(filters_orig, set):
|
195
|
200
|
for i, cfilt in enumerate(filters_orig):
|
196
|
201
|
a, b, c = cfilt
|
197
|
|
- if isinstance(c, list): #list are not hashable
|
|
202
|
+ if isinstance(c, list): # list are not hashable
|
198
|
203
|
newc = tuple(c)
|
199
|
204
|
else:
|
200
|
205
|
newc = c
|
201
|
206
|
old_len = len(filters_cp)
|
202
|
|
- filters_cp |= set((a,b,newc))
|
|
207
|
+ filters_cp |= set((a, b, newc))
|
203
|
208
|
if len(filters_cp) == old_len:
|
204
|
209
|
del(filters_orig[i])
|
205
|
210
|
# Sets _query_filter attribute of self query
|
206
|
211
|
self._query_filter = (filters_orig, result_rel_filters)
|
207
|
212
|
|
208
|
|
- #Sub queries creation
|
|
213
|
+ # Sub queries creation
|
209
|
214
|
subq = list()
|
210
|
215
|
for ds, rfilters in other_ds_filters.items():
|
211
|
216
|
for rfilter in rfilters:
|
|
@@ -218,7 +223,7 @@ class LeFilteredQuery(LeQuery):
|
218
|
223
|
subq.append((rfield, query))
|
219
|
224
|
self.subqueries = subq
|
220
|
225
|
|
221
|
|
- ##@return informations
|
|
226
|
+ # @return informations
|
222
|
227
|
def dump_infos(self):
|
223
|
228
|
ret = super().dump_infos()
|
224
|
229
|
ret['query_filter'] = self._query_filter
|
|
@@ -238,16 +243,16 @@ class LeFilteredQuery(LeQuery):
|
238
|
243
|
res += '>'
|
239
|
244
|
return res
|
240
|
245
|
|
241
|
|
- ## @brief Prepare filters for datasource
|
|
246
|
+ # @brief Prepare filters for datasource
|
242
|
247
|
#
|
243
|
|
- #A filter can be a string or a tuple with len = 3.
|
|
248
|
+ # A filter can be a string or a tuple with len = 3.
|
244
|
249
|
#
|
245
|
|
- #This method divide filters in two categories :
|
|
250
|
+ # This method divide filters in two categories :
|
246
|
251
|
#
|
247
|
252
|
#@par Simple filters
|
248
|
253
|
#
|
249
|
|
- #Those filters concerns fields that represent object values (a title,
|
250
|
|
- #the content, etc.) They are composed of three elements : FIELDNAME OP
|
|
254
|
+ # Those filters concerns fields that represent object values (a title,
|
|
255
|
+ # the content, etc.) They are composed of three elements : FIELDNAME OP
|
251
|
256
|
# VALUE . Where :
|
252
|
257
|
#- FIELDNAME is the name of the field
|
253
|
258
|
#- OP is one of the authorized comparison operands (see
|
|
@@ -256,14 +261,14 @@ class LeFilteredQuery(LeQuery):
|
256
|
261
|
#
|
257
|
262
|
#@par Relational filters
|
258
|
263
|
#
|
259
|
|
- #Those filters concerns on reference fields (see the corresponding
|
260
|
|
- #abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
|
261
|
|
- #The filter as quite the same composition than simple filters :
|
|
264
|
+ # Those filters concerns on reference fields (see the corresponding
|
|
265
|
+ # abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
|
|
266
|
+ # The filter as quite the same composition than simple filters :
|
262
|
267
|
# FIELDNAME[.REF_FIELD] OP VALUE . Where :
|
263
|
268
|
#- FIELDNAME is the name of the reference field
|
264
|
269
|
#- REF_FIELD is an optionnal addon to the base field. It indicate on wich
|
265
|
|
- #field of the referenced object the comparison as to be done. If no
|
266
|
|
- #REF_FIELD is indicated the comparison will be done on identifier.
|
|
270
|
+ # field of the referenced object the comparison as to be done. If no
|
|
271
|
+ # REF_FIELD is indicated the comparison will be done on identifier.
|
267
|
272
|
#
|
268
|
273
|
#@param cls
|
269
|
274
|
#@param filters_l list : This list of str or tuple (or both)
|
|
@@ -271,11 +276,11 @@ class LeFilteredQuery(LeQuery):
|
271
|
276
|
#@todo move this doc in another place (a dedicated page ?)
|
272
|
277
|
#@warning Does not supports multiple UID for an EmClass
|
273
|
278
|
def _prepare_filters(self, filters_l):
|
274
|
|
- filters=list()
|
|
279
|
+ filters = list()
|
275
|
280
|
res_filters = list()
|
276
|
281
|
rel_filters = list()
|
277
|
282
|
err_l = dict()
|
278
|
|
- #Splitting in tuple if necessary
|
|
283
|
+ # Splitting in tuple if necessary
|
279
|
284
|
for i, fil in enumerate(filters_l):
|
280
|
285
|
if len(fil) == 3 and not isinstance(fil, str):
|
281
|
286
|
filters.append(tuple(fil))
|
|
@@ -286,7 +291,7 @@ class LeFilteredQuery(LeQuery):
|
286
|
291
|
err_l["filter %d" % i] = e
|
287
|
292
|
|
288
|
293
|
for field, operator, value in filters:
|
289
|
|
- err_key = "%s %s %s" % (field, operator, value) #to push in err_l
|
|
294
|
+ err_key = "%s %s %s" % (field, operator, value) # to push in err_l
|
290
|
295
|
# Spliting field name to be able to detect a relational field
|
291
|
296
|
field_spl = field.split('.')
|
292
|
297
|
if len(field_spl) == 2:
|
|
@@ -310,12 +315,12 @@ field name" % field)
|
310
|
315
|
# inconsistency
|
311
|
316
|
err_l[field] = NameError("The field '%s' in %s is not \
|
312
|
317
|
a relational field, but %s.%s was present in the filter"
|
313
|
|
- % (field,
|
314
|
|
- self._target_class.__name__,
|
315
|
|
- field,
|
316
|
|
- ref_field))
|
|
318
|
+ % (field,
|
|
319
|
+ self._target_class.__name__,
|
|
320
|
+ field,
|
|
321
|
+ ref_field))
|
317
|
322
|
if field_datahandler.is_reference():
|
318
|
|
- #Relationnal field
|
|
323
|
+ # Relationnal field
|
319
|
324
|
if ref_field is None:
|
320
|
325
|
# ref_field default value
|
321
|
326
|
#
|
|
@@ -350,14 +355,14 @@ field to use for the relational filter"
|
350
|
355
|
value, error = field_datahandler.check_data_value(value)
|
351
|
356
|
if isinstance(error, Exception):
|
352
|
357
|
value = value_orig
|
353
|
|
- res_filters.append((field,operator, value))
|
|
358
|
+ res_filters.append((field, operator, value))
|
354
|
359
|
if len(err_l) > 0:
|
355
|
360
|
raise LeApiDataCheckErrors(
|
356
|
|
- "Error while preparing filters : ",
|
357
|
|
- err_l)
|
|
361
|
+ "Error while preparing filters : ",
|
|
362
|
+ err_l)
|
358
|
363
|
return (res_filters, rel_filters)
|
359
|
364
|
|
360
|
|
- ## @brief Check and split a query filter
|
|
365
|
+ # @brief Check and split a query filter
|
361
|
366
|
# @note The query_filter format is "FIELD OPERATOR VALUE"
|
362
|
367
|
# @param query_filter str : A query_filter string
|
363
|
368
|
# @param cls
|
|
@@ -382,18 +387,18 @@ field to use for the relational filter"
|
382
|
387
|
raise ValueError(msg % query_filter)
|
383
|
388
|
return result
|
384
|
389
|
|
385
|
|
- ## @brief Compile the regex for query_filter processing
|
|
390
|
+ # @brief Compile the regex for query_filter processing
|
386
|
391
|
# @note Set _LeObject._query_re
|
387
|
392
|
@classmethod
|
388
|
393
|
def __compile_query_re(cls):
|
389
|
394
|
op_re_piece = '(?P<operator>(%s)'
|
390
|
395
|
op_re_piece %= cls._query_operators[0].replace(' ', '\s')
|
391
|
396
|
for operator in cls._query_operators[1:]:
|
392
|
|
- op_re_piece += '|(%s)'%operator.replace(' ', '\s')
|
|
397
|
+ op_re_piece += '|(%s)' % operator.replace(' ', '\s')
|
393
|
398
|
op_re_piece += ')'
|
394
|
399
|
|
395
|
400
|
re_full = '^\s*(?P<field>([a-z_][a-z0-9\-_]*\.)?[a-z_][a-z0-9\-_]*)\s*'
|
396
|
|
- re_full += op_re_piece+'\s*(?P<value>.*)\s*$'
|
|
401
|
+ re_full += op_re_piece + '\s*(?P<value>.*)\s*$'
|
397
|
402
|
|
398
|
403
|
cls._query_re = re.compile(re_full, flags=re.IGNORECASE)
|
399
|
404
|
pass
|
|
@@ -407,10 +412,10 @@ field to use for the relational filter"
|
407
|
412
|
msg %= (fieldname, target_class.__name__)
|
408
|
413
|
return NameError(msg)
|
409
|
414
|
|
410
|
|
- ##@brief Prepare a relational filter
|
|
415
|
+ # @brief Prepare a relational filter
|
411
|
416
|
#
|
412
|
|
- #Relational filters are composed of a tuple like the simple filters
|
413
|
|
- #but the first element of this tuple is a tuple to :
|
|
417
|
+ # Relational filters are composed of a tuple like the simple filters
|
|
418
|
+ # but the first element of this tuple is a tuple to :
|
414
|
419
|
#
|
415
|
420
|
#<code>((FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
|
416
|
421
|
# Where :
|
|
@@ -419,9 +424,9 @@ field to use for the relational filter"
|
419
|
424
|
# - REF_CLASS as key. It's a LeObject child class
|
420
|
425
|
# - REF_FIELD as value. The name of the referenced field in the REF_CLASS
|
421
|
426
|
#
|
422
|
|
- #Visibly the REF_FIELD value of the dict will vary only when
|
423
|
|
- #no REF_FIELD is explicitly given in the filter string notation
|
424
|
|
- #and REF_CLASSES has differents uid
|
|
427
|
+ # Visibly the REF_FIELD value of the dict will vary only when
|
|
428
|
+ # no REF_FIELD is explicitly given in the filter string notation
|
|
429
|
+ # and REF_CLASSES has differents uid
|
425
|
430
|
#
|
426
|
431
|
#@par String notation examples
|
427
|
432
|
#<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
|
|
@@ -439,7 +444,7 @@ field to use for the relational filter"
|
439
|
444
|
#
|
440
|
445
|
#@param fieldname str : The relational field name
|
441
|
446
|
#@param ref_field str|None : The referenced field name (if None use
|
442
|
|
- #uniq identifiers as referenced field
|
|
447
|
+ # uniq identifiers as referenced field
|
443
|
448
|
#@return a well formed relational filter tuple or an Exception instance
|
444
|
449
|
def _prepare_relational_fields(self, fieldname, ref_field=None):
|
445
|
450
|
datahandler = self._target_class.field(fieldname)
|
|
@@ -467,12 +472,12 @@ the relational filter %s"
|
467
|
472
|
logger.debug(msg)
|
468
|
473
|
if len(ref_dict) == 0:
|
469
|
474
|
return NameError("No field named '%s' in referenced objects [%s]"
|
470
|
|
- % (ref_field,
|
471
|
|
- ','.join([rc.__name__ for rc in ref_classes])))
|
|
475
|
+ % (ref_field,
|
|
476
|
+ ','.join([rc.__name__ for rc in ref_classes])))
|
472
|
477
|
return (fieldname, ref_dict)
|
473
|
478
|
|
474
|
479
|
|
475
|
|
-##@brief A query to insert a new object
|
|
480
|
+# @brief A query to insert a new object
|
476
|
481
|
class LeInsertQuery(LeQuery):
|
477
|
482
|
_hook_prefix = 'leapi_insert_'
|
478
|
483
|
_data_check_args = {'complete': True, 'allow_internal': False}
|
|
@@ -483,49 +488,49 @@ class LeInsertQuery(LeQuery):
|
483
|
488
|
abstract LeObject : %s" % target_class)
|
484
|
489
|
super().__init__(target_class)
|
485
|
490
|
|
486
|
|
- ## @brief Implements an insert query operation, with only one insertion
|
487
|
|
- # @param datas : datas to be inserted
|
488
|
|
- def _query(self, datas):
|
489
|
|
- datas = self._target_class.prepare_datas(datas, True, False)
|
490
|
|
- id_inserted = self._rw_datasource.insert(self._target_class, datas)
|
|
491
|
+ # @brief Implements an insert query operation, with only one insertion
|
|
492
|
+ # @param data : data to be inserted
|
|
493
|
+ def _query(self, data):
|
|
494
|
+ data = self._target_class.prepare_datas(data, True, False)
|
|
495
|
+ id_inserted = self._rw_datasource.insert(self._target_class, data)
|
491
|
496
|
return id_inserted
|
492
|
497
|
"""
|
493
|
498
|
## @brief Implements an insert query operation, with multiple insertions
|
494
|
|
- # @param datas : list of **datas to be inserted
|
495
|
|
- def _query(self, datas):
|
|
499
|
+ # @param data : list of **data to be inserted
|
|
500
|
+ def _query(self, data):
|
496
|
501
|
nb_inserted = self._datasource.insert_multi(
|
497
|
|
- self._target_class,datas_list)
|
|
502
|
+ self._target_class,data_list)
|
498
|
503
|
if nb_inserted < 0:
|
499
|
504
|
raise LeApiQueryError("Multiple insertions error")
|
500
|
505
|
return nb_inserted
|
501
|
506
|
"""
|
502
|
507
|
|
503
|
|
- ## @brief Execute the insert query
|
504
|
|
- def execute(self, datas):
|
505
|
|
- return super().execute(datas=datas)
|
|
508
|
+ # @brief Execute the insert query
|
|
509
|
+ def execute(self, data):
|
|
510
|
+ return super().execute(data=data)
|
506
|
511
|
|
507
|
512
|
|
508
|
|
-##@brief A query to update datas for a given object
|
|
513
|
+# @brief A query to update data for a given object
|
509
|
514
|
#
|
510
|
515
|
#@todo Change behavior, Huge optimization problem when updating using filters
|
511
|
|
-#and not instance. We have to run a GET and then 1 update by fecthed object...
|
|
516
|
+# and not instance. We have to run a GET and then 1 update by fecthed object...
|
512
|
517
|
class LeUpdateQuery(LeFilteredQuery):
|
513
|
518
|
_hook_prefix = 'leapi_update_'
|
514
|
519
|
_data_check_args = {'complete': False, 'allow_internal': False}
|
515
|
520
|
|
516
|
|
- ##@brief Instanciate an update query
|
|
521
|
+ # @brief Instanciate an update query
|
517
|
522
|
#
|
518
|
|
- #If a class and not an instance is given, no query_filters are expected
|
519
|
|
- #and the update will be fast and simple. Else we have to run a get query
|
520
|
|
- #before updating (to fetch datas, update them and then, construct them
|
521
|
|
- #and check their consistency)
|
|
523
|
+ # If a class and not an instance is given, no query_filters are expected
|
|
524
|
+ # and the update will be fast and simple. Else we have to run a get query
|
|
525
|
+ # before updating (to fetch data, update them and then, construct them
|
|
526
|
+ # and check their consistency)
|
522
|
527
|
#@param target LeObject clas or instance
|
523
|
528
|
#@param query_filters list|None
|
524
|
|
- #@todo change strategy with instance update. We have to accept datas for
|
525
|
|
- #the execute method
|
|
529
|
+ #@todo change strategy with instance update. We have to accept data for
|
|
530
|
+ # the execute method
|
526
|
531
|
def __init__(self, target, query_filters=None):
|
527
|
|
- ##@brief This attr is set only if the target argument is an
|
528
|
|
- #instance of a LeObject subclass
|
|
532
|
+ # @brief This attr is set only if the target argument is an
|
|
533
|
+ # instance of a LeObject subclass
|
529
|
534
|
self.__leobject_instance_datas = None
|
530
|
535
|
target_class = target
|
531
|
536
|
|
|
@@ -542,16 +547,16 @@ target to LeUpdateQuery constructor"
|
542
|
547
|
|
543
|
548
|
super().__init__(target_class, query_filters)
|
544
|
549
|
|
545
|
|
- ##@brief Implements an update query
|
546
|
|
- #@param datas dict : datas to update
|
|
550
|
+ # @brief Implements an update query
|
|
551
|
+ #@param data dict : data to be updated
|
547
|
552
|
#@returns the number of updated items
|
548
|
|
- #@todo change stategy for instance update. Datas should be allowed
|
549
|
|
- #for execute method (and query)
|
550
|
|
- def _query(self, datas):
|
|
553
|
+ #@todo change stategy for instance update. Data should be allowed
|
|
554
|
+ # for execute method (and query)
|
|
555
|
+ def _query(self, data):
|
551
|
556
|
uid_name = self._target_class._uid[0]
|
552
|
557
|
if self.__leobject_instance_datas is not None:
|
553
|
|
- #Instance update
|
554
|
|
- #Building query_filter
|
|
558
|
+ # Instance update
|
|
559
|
+ # Building query_filter
|
555
|
560
|
filters = [(
|
556
|
561
|
uid_name,
|
557
|
562
|
'=',
|
|
@@ -560,59 +565,60 @@ target to LeUpdateQuery constructor"
|
560
|
565
|
self._target_class, filters, [],
|
561
|
566
|
self.__leobject_instance_datas)
|
562
|
567
|
else:
|
563
|
|
- #Update by filters, we have to fetch datas before updating
|
|
568
|
+ # Update by filters, we have to fetch data before updating
|
564
|
569
|
res = self._ro_datasource.select(
|
565
|
570
|
self._target_class, self._target_class.fieldnames(True),
|
566
|
571
|
self._query_filter[0],
|
567
|
572
|
self._query_filter[1])
|
568
|
|
- #Checking and constructing datas
|
569
|
|
- upd_datas = dict()
|
|
573
|
+ # Checking and constructing data
|
|
574
|
+ upd_data = dict()
|
570
|
575
|
for res_data in res:
|
571
|
|
- res_data.update(datas)
|
572
|
|
- res_datas = self._target_class.prepare_datas(
|
|
576
|
+ res_data.update(data)
|
|
577
|
+ res_data = self._target_class.prepare_datas(
|
573
|
578
|
res_data, True, True)
|
574
|
579
|
filters = [(uid_name, '=', res_data[uid_name])]
|
575
|
580
|
res = self._rw_datasource.update(
|
576
|
581
|
self._target_class, filters, [],
|
577
|
|
- res_datas)
|
|
582
|
+ res_data)
|
578
|
583
|
return res
|
579
|
584
|
|
580
|
|
- ## @brief Execute the update query
|
581
|
|
- def execute(self, datas=None):
|
582
|
|
- if self.__leobject_instance_datas is not None and datas is not None:
|
583
|
|
- raise LeApiQueryError("No datas expected when running an update \
|
|
585
|
+ # @brief Execute the update query
|
|
586
|
+ def execute(self, data=None):
|
|
587
|
+ if self.__leobject_instance_datas is not None and data is not None:
|
|
588
|
+ raise LeApiQueryError("No data expected when running an update \
|
584
|
589
|
query on an instance")
|
585
|
|
- if self.__leobject_instance_datas is None and datas is None:
|
586
|
|
- raise LeApiQueryError("Datas are mandatory when running an update \
|
|
590
|
+ if self.__leobject_instance_datas is None and data is None:
|
|
591
|
+ raise LeApiQueryError("Data are mandatory when running an update \
|
587
|
592
|
query on a class with filters")
|
588
|
|
- return super().execute(datas=datas)
|
|
593
|
+ return super().execute(data=data)
|
589
|
594
|
|
590
|
595
|
|
591
|
|
-##@brief A query to delete an object
|
|
596
|
+# @brief A query to delete an object
|
592
|
597
|
class LeDeleteQuery(LeFilteredQuery):
|
593
|
598
|
_hook_prefix = 'leapi_delete_'
|
594
|
599
|
|
595
|
600
|
def __init__(self, target_class, query_filter):
|
596
|
601
|
super().__init__(target_class, query_filter)
|
597
|
602
|
|
598
|
|
- ## @brief Execute the delete query
|
599
|
|
- # @param datas
|
600
|
|
- def execute(self, datas=None):
|
|
603
|
+ # @brief Execute the delete query
|
|
604
|
+ # @param data
|
|
605
|
+ def execute(self, data=None):
|
601
|
606
|
return super().execute()
|
602
|
607
|
|
603
|
|
- ##@brief Implements delete query operations
|
604
|
|
- # @param datas
|
|
608
|
+ # @brief Implements delete query operations
|
|
609
|
+ # @param data
|
605
|
610
|
#@returns the number of deleted items
|
606
|
|
- def _query(self, datas=None):
|
|
611
|
+ def _query(self, data=None):
|
607
|
612
|
filters, rel_filters = self._query_filter
|
608
|
613
|
nb_deleted = self._rw_datasource.delete(
|
609
|
614
|
self._target_class, filters, rel_filters)
|
610
|
615
|
return nb_deleted
|
611
|
616
|
|
|
617
|
+
|
612
|
618
|
class LeGetQuery(LeFilteredQuery):
|
613
|
619
|
_hook_prefix = 'leapi_get_'
|
614
|
620
|
|
615
|
|
- ##@brief Instanciate a new get query
|
|
621
|
+ # @brief Instanciate a new get query
|
616
|
622
|
#@param target_class LeObject : class of object the query is about
|
617
|
623
|
#@param query_filters dict : {OP, list of query filters}
|
618
|
624
|
# or tuple (FIELD, OPERATOR, VALUE) )
|
|
@@ -624,33 +630,33 @@ class LeGetQuery(LeFilteredQuery):
|
624
|
630
|
# - offset int : offset
|
625
|
631
|
def __init__(self, target_class, query_filters, **kwargs):
|
626
|
632
|
super().__init__(target_class, query_filters)
|
627
|
|
- ##@brief The fields to get
|
|
633
|
+ # @brief The fields to get
|
628
|
634
|
self._field_list = None
|
629
|
|
- ##@brief An equivalent to the SQL ORDER BY
|
|
635
|
+ # @brief An equivalent to the SQL ORDER BY
|
630
|
636
|
self._order = None
|
631
|
|
- ##@brief An equivalent to the SQL GROUP BY
|
|
637
|
+ # @brief An equivalent to the SQL GROUP BY
|
632
|
638
|
self._group = None
|
633
|
|
- ##@brief An equivalent to the SQL LIMIT x
|
|
639
|
+ # @brief An equivalent to the SQL LIMIT x
|
634
|
640
|
self._limit = None
|
635
|
|
- ##@brief An equivalent to the SQL LIMIT x, OFFSET
|
|
641
|
+ # @brief An equivalent to the SQL LIMIT x, OFFSET
|
636
|
642
|
self._offset = 0
|
637
|
643
|
|
638
|
644
|
# Checking kwargs and assigning default values if there is some
|
639
|
645
|
for argname in kwargs:
|
640
|
646
|
if argname not in (
|
641
|
|
- 'field_list', 'order', 'group', 'limit', 'offset'):
|
|
647
|
+ 'field_list', 'order', 'group', 'limit', 'offset'):
|
642
|
648
|
raise TypeError("Unexpected argument '%s'" % argname)
|
643
|
649
|
|
644
|
650
|
if 'field_list' not in kwargs:
|
645
|
|
- self.set_field_list(target_class.fieldnames(include_ro = True))
|
|
651
|
+ self.set_field_list(target_class.fieldnames(include_ro=True))
|
646
|
652
|
else:
|
647
|
653
|
self.set_field_list(kwargs['field_list'])
|
648
|
654
|
|
649
|
655
|
if 'order' in kwargs:
|
650
|
|
- #check kwargs['order']
|
|
656
|
+ # check kwargs['order']
|
651
|
657
|
self._order = kwargs['order']
|
652
|
658
|
if 'group' in kwargs:
|
653
|
|
- #check kwargs['group']
|
|
659
|
+ # check kwargs['group']
|
654
|
660
|
self._group = kwargs['group']
|
655
|
661
|
if 'limit' in kwargs and kwargs['limit'] is not None:
|
656
|
662
|
try:
|
|
@@ -669,7 +675,7 @@ class LeGetQuery(LeFilteredQuery):
|
669
|
675
|
msg = "offset argument expected to be an integer >= 0"
|
670
|
676
|
raise ValueError(msg)
|
671
|
677
|
|
672
|
|
- ##@brief Set the field list
|
|
678
|
+ # @brief Set the field list
|
673
|
679
|
# @param field_list list | None : If None use all fields
|
674
|
680
|
# @return None
|
675
|
681
|
# @throw LeApiQueryError if unknown field given
|
|
@@ -682,41 +688,41 @@ class LeGetQuery(LeFilteredQuery):
|
682
|
688
|
msg = "No field named '%s' in %s"
|
683
|
689
|
msg %= (fieldname, self._target_class.__name__)
|
684
|
690
|
expt = NameError(msg)
|
685
|
|
- err_l[fieldname] = expt
|
|
691
|
+ err_l[fieldname] = expt
|
686
|
692
|
if len(err_l) > 0:
|
687
|
693
|
msg = "Error while setting field_list in a get query"
|
688
|
|
- raise LeApiQueryErrors(msg = msg, exceptions = err_l)
|
|
694
|
+ raise LeApiQueryErrors(msg=msg, exceptions=err_l)
|
689
|
695
|
self._field_list = list(set(field_list))
|
690
|
696
|
|
691
|
|
- ##@brief Execute the get query
|
692
|
|
- def execute(self, datas=None):
|
|
697
|
+ # @brief Execute the get query
|
|
698
|
+ def execute(self, data=None):
|
693
|
699
|
return super().execute()
|
694
|
700
|
|
695
|
|
- ##@brief Implements select query operations
|
|
701
|
+ # @brief Implements select query operations
|
696
|
702
|
# @returns a list containing the item(s)
|
697
|
|
- def _query(self, datas=None):
|
698
|
|
- # select datas corresponding to query_filter
|
|
703
|
+ def _query(self, data=None):
|
|
704
|
+ # select data corresponding to query_filter
|
699
|
705
|
fl = list(self._field_list) if self._field_list is not None else None
|
700
|
|
- l_datas=self._ro_datasource.select(
|
701
|
|
- target = self._target_class,
|
702
|
|
- field_list = fl,
|
703
|
|
- filters = self._query_filter[0],
|
704
|
|
- relational_filters = self._query_filter[1],
|
705
|
|
- order = self._order,
|
706
|
|
- group = self._group,
|
707
|
|
- limit = self._limit,
|
708
|
|
- offset = self._offset)
|
709
|
|
- return l_datas
|
710
|
|
-
|
711
|
|
- ##@return a dict with query infos
|
|
706
|
+ l_data = self._ro_datasource.select(
|
|
707
|
+ target=self._target_class,
|
|
708
|
+ field_list=fl,
|
|
709
|
+ filters=self._query_filter[0],
|
|
710
|
+ relational_filters=self._query_filter[1],
|
|
711
|
+ order=self._order,
|
|
712
|
+ group=self._group,
|
|
713
|
+ limit=self._limit,
|
|
714
|
+ offset=self._offset)
|
|
715
|
+ return l_data
|
|
716
|
+
|
|
717
|
+ # @return a dict with query infos
|
712
|
718
|
def dump_infos(self):
|
713
|
719
|
ret = super().dump_infos()
|
714
|
|
- ret.update({ 'field_list' : self._field_list,
|
715
|
|
- 'order' : self._order,
|
716
|
|
- 'group' : self._group,
|
717
|
|
- 'limit' : self._limit,
|
718
|
|
- 'offset': self._offset,
|
719
|
|
- })
|
|
720
|
+ ret.update({'field_list': self._field_list,
|
|
721
|
+ 'order': self._order,
|
|
722
|
+ 'group': self._group,
|
|
723
|
+ 'limit': self._limit,
|
|
724
|
+ 'offset': self._offset,
|
|
725
|
+ })
|
720
|
726
|
return ret
|
721
|
727
|
|
722
|
728
|
def __repr__(self):
|
|
@@ -725,7 +731,7 @@ field_list={field_list} order={order} group={group} limit={limit} \
|
725
|
731
|
offset={offset}"
|
726
|
732
|
res = res.format(**self.dump_infos())
|
727
|
733
|
if len(self.subqueries) > 0:
|
728
|
|
- for n,subq in enumerate(self.subqueries):
|
|
734
|
+ for n, subq in enumerate(self.subqueries):
|
729
|
735
|
res += "\n\tSubquerie %d : %s"
|
730
|
736
|
res %= (n, subq)
|
731
|
737
|
res += ">"
|