|
@@ -1,6 +1,7 @@
|
1
|
1
|
#-*- coding: utf-8 -*-
|
2
|
|
-
|
3
|
|
-# @package lodel.leapi.datahandlers.base_classes Define all base/abstract class for data handlers
|
|
2
|
+##
|
|
3
|
+# @package lodel.leapi.datahandlers.base_classes Defines all base/abstract
|
|
4
|
+# classes for DataHandlers
|
4
|
5
|
#
|
5
|
6
|
# Contains custom exceptions too
|
6
|
7
|
|
|
@@ -30,15 +31,33 @@ LodelContext.expose_modules(globals(), {
|
30
|
31
|
'lodel.logger': 'logger',
|
31
|
32
|
'lodel.utils.mlstring': ['MlString']})
|
32
|
33
|
|
33
|
|
-
|
34
|
|
-# @brief Base class for all data handlers
|
|
34
|
+##
|
|
35
|
+# @brief Base class for all DataHandlers
|
35
|
36
|
# @ingroup lodel2_datahandlers
|
|
37
|
+#
|
|
38
|
+# @remarks Some of the methods and properties in this "abstract" class are
|
|
39
|
+# bounded to its children. This implies that the parent
|
|
40
|
+# is aware of its children, which is an absolute anti-pattern
|
|
41
|
+# (Liskov / OC violation), a source of confusion and a decrased
|
|
42
|
+# maintainability. Aggregation =/= Inheritance
|
|
43
|
+# Concerned methods are: is_reference; is_singlereference.
|
|
44
|
+# Concerned properties are __custom_datahandlers; base_handlers.
|
|
45
|
+# @remarks What is the purpose of an internal property being set to a
|
|
46
|
+# string (namely 'automatic')
|
|
47
|
+# @remarks Two sets of methods appears a little strange in regards to their
|
|
48
|
+# visibility.
|
|
49
|
+# - @ref _construct_data / @ref construct_data
|
|
50
|
+# - @ref _check_data_consistency / @ref check_data_consistency
|
36
|
51
|
class DataHandler(MlNamedObject):
|
37
|
52
|
base_type = "type"
|
38
|
53
|
_HANDLERS_MODULES = ('datas_base', 'datas', 'references')
|
39
|
|
- # @brief Stores the DataHandler childs classes indexed by name
|
|
54
|
+
|
|
55
|
+ ##
|
|
56
|
+ # @brief Stores the DataHandler child classes indexed by name
|
40
|
57
|
_base_handlers = None
|
41
|
|
- # @brief Stores custom datahandlers classes indexed by name
|
|
58
|
+
|
|
59
|
+ ##
|
|
60
|
+ # @brief Stores custom DataHandlers classes indexed by name
|
42
|
61
|
# @todo do it ! (like plugins, register handlers... blablabla)
|
43
|
62
|
__custom_handlers = dict()
|
44
|
63
|
|
|
@@ -47,17 +66,22 @@ class DataHandler(MlNamedObject):
|
47
|
66
|
options_spec = dict()
|
48
|
67
|
options_values = dict()
|
49
|
68
|
|
50
|
|
- # @brief List fields that will be exposed to the construct_data_method
|
|
69
|
+ ##
|
|
70
|
+ # @brief Lists fields that will be exposed to the construct_data method
|
51
|
71
|
_construct_datas_deps = []
|
52
|
72
|
|
53
|
73
|
directly_editable = True
|
54
|
74
|
|
|
75
|
+
|
|
76
|
+ ##
|
55
|
77
|
# @brief constructor
|
56
|
78
|
#
|
57
|
79
|
# @param internal False | str : define whether or not a field is internal
|
58
|
|
- # @param immutable bool : indicates if the fieldtype has to be defined in child classes of
|
|
80
|
+ # @param immutable bool : Indicates if the fieldtype has to be defined in child classes of
|
59
|
81
|
# LeObject or if it is designed globally and immutable
|
60
|
|
- # @throw NotImplementedError if it is instanciated directly
|
|
82
|
+ # @throw NotImplementedError If it is instantiated directly
|
|
83
|
+ # @remarks Shouldn't the class be declared abstract? No need to check if it
|
|
84
|
+ # is instantiated directly, no exception to throw, cleaner code.
|
61
|
85
|
def __init__(self, **kwargs):
|
62
|
86
|
if self.__class__ == DataHandler:
|
63
|
87
|
raise NotImplementedError("Abstract class")
|
|
@@ -80,9 +104,12 @@ class DataHandler(MlNamedObject):
|
80
|
104
|
help_text = kwargs.get('help_text', MlString(self.help_text))
|
81
|
105
|
super().__init__(display_name, help_text)
|
82
|
106
|
|
83
|
|
- # @brief Sets properly casted and checked options for the datahandler
|
84
|
|
- # @raises LodelDataHandlerNotAllowedOptionException when a passed option is not in the option
|
85
|
|
- # specifications of the datahandler
|
|
107
|
+
|
|
108
|
+ ##
|
|
109
|
+ # @brief Sets properly cast and checked options for the DataHandler
|
|
110
|
+ #
|
|
111
|
+ # @throw LodelDataHandlerNotAllowedOptionException when a passed option
|
|
112
|
+ # is not in the option specifications of the DataHandler
|
86
|
113
|
def check_options(self):
|
87
|
114
|
for option_name, option_datas in self.options_spec.items():
|
88
|
115
|
if option_name in self.options_values:
|
|
@@ -96,42 +123,70 @@ class DataHandler(MlNamedObject):
|
96
|
123
|
# This option was not configured, we get the default value from the specs
|
97
|
124
|
self.options_values[option_name] = option_datas[0]
|
98
|
125
|
|
99
|
|
- # Fieldtype name
|
|
126
|
+
|
|
127
|
+ ##
|
|
128
|
+ # @return string: Field type name
|
100
|
129
|
@classmethod
|
101
|
130
|
def name(cls):
|
102
|
131
|
return cls.__module__.split('.')[-1]
|
103
|
132
|
|
|
133
|
+
|
|
134
|
+ ##
|
|
135
|
+ # @return bool: True if subclass is of Reference type, False otherwise.
|
104
|
136
|
@classmethod
|
105
|
137
|
def is_reference(cls):
|
106
|
138
|
return issubclass(cls, Reference)
|
107
|
139
|
|
|
140
|
+
|
|
141
|
+ ##
|
|
142
|
+ # @return bool: True if subclass is of SingleRef type, False otherwise.
|
108
|
143
|
@classmethod
|
109
|
144
|
def is_singlereference(cls):
|
110
|
145
|
return issubclass(cls, SingleRef)
|
111
|
146
|
|
|
147
|
+
|
|
148
|
+ ##
|
|
149
|
+ # @return bool: True if the field is a primary_key, False otherwise.
|
112
|
150
|
def is_primary_key(self):
|
113
|
151
|
return self.primary_key
|
114
|
152
|
|
115
|
|
- # @brief checks if a fieldtype is internal
|
116
|
|
- # @return bool
|
|
153
|
+
|
|
154
|
+ ##
|
|
155
|
+ # @brief checks if a field type is internal
|
|
156
|
+ # @return bool: True if the field is internal, False otherwise.
|
117
|
157
|
def is_internal(self):
|
118
|
158
|
return self.internal is not False
|
119
|
159
|
|
|
160
|
+
|
|
161
|
+ ##
|
120
|
162
|
# @brief check if a value can be nullable
|
|
163
|
+ #
|
121
|
164
|
# @param value *
|
122
|
|
- # @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
|
|
165
|
+ # @throw DataNoneValid if value is None and nullable.
|
|
166
|
+ # @throw LodelExceptions if not nullable
|
123
|
167
|
# @return value (if not None)
|
124
|
168
|
# @return value
|
|
169
|
+ #
|
|
170
|
+ # @remarks why are there an thrown exception if it is allowed?
|
|
171
|
+ # Exceptions are no message brokers
|
125
|
172
|
def _check_data_value(self, value):
|
126
|
173
|
if value is None:
|
127
|
174
|
if not self.nullable:
|
128
|
175
|
raise LodelExceptions("None value is forbidden for this data field")
|
129
|
|
- raise DataNoneValid("None with a nullable. This exeption is allowed")
|
|
176
|
+ raise DataNoneValid("None with a nullable. This exception is allowed")
|
130
|
177
|
return value
|
131
|
178
|
|
|
179
|
+
|
|
180
|
+ ##
|
132
|
181
|
# @brief calls the data_field (defined in derived class) _check_data_value() method
|
133
|
182
|
# @param value *
|
134
|
183
|
# @return tuple (value|None, None|error) value can be cast if NoneError
|
|
184
|
+ # @remarks Consider renaming this method, such as '_is_data_nullable'.
|
|
185
|
+ # @remarks Exceptions ARE NOT message brokers! Moreover, those two methods
|
|
186
|
+ # are more complicated than required. In case 'value' is None,
|
|
187
|
+ # the returned value is the same as the input value. This is the
|
|
188
|
+ # same behavior as when the value is not None!
|
|
189
|
+ # @return What's a "NoneError"? Value can be cast to what?
|
135
|
190
|
def check_data_value(self, value):
|
136
|
191
|
try:
|
137
|
192
|
value = self._check_data_value(value)
|
|
@@ -141,25 +196,36 @@ class DataHandler(MlNamedObject):
|
141
|
196
|
return None, expt
|
142
|
197
|
return value, None
|
143
|
198
|
|
144
|
|
- # @brief checks if this class can override the given data handler
|
|
199
|
+ ##
|
|
200
|
+ # @brief Checks if this class can override the given data handler.
|
|
201
|
+ # i.e. both class having the same base_type.
|
145
|
202
|
# @param data_handler DataHandler
|
146
|
203
|
# @return bool
|
|
204
|
+ # @remarks Simplify by "return data_handler.__class__.base_type == self.__class__.base_type"?
|
147
|
205
|
def can_override(self, data_handler):
|
148
|
206
|
if data_handler.__class__.base_type != self.__class__.base_type:
|
149
|
207
|
return False
|
150
|
208
|
return True
|
151
|
209
|
|
|
210
|
+
|
|
211
|
+ ##
|
152
|
212
|
# @brief Build field value
|
|
213
|
+ #
|
153
|
214
|
# @ingroup lodel2_dh_checks
|
154
|
|
- # @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
155
|
215
|
# @ref _construct_data() and @ref lodel2_dh_check_impl )
|
|
216
|
+ #
|
156
|
217
|
# @param emcomponent EmComponent : An EmComponent child class instance
|
157
|
218
|
# @param fname str : The field name
|
158
|
219
|
# @param datas dict : dict storing fields values (from the component)
|
159
|
220
|
# @param cur_value : the value from the current field (identified by fieldname)
|
160
|
221
|
# @return the value
|
161
|
222
|
# @throw RunTimeError if data construction fails
|
|
223
|
+ #
|
|
224
|
+ # @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
162
|
225
|
# @todo raise something else
|
|
226
|
+ #
|
|
227
|
+ # @remarks What the todo up right here means? Raise what? When?
|
|
228
|
+ # @remarks Nothing is being raised in this method, should it?
|
163
|
229
|
def construct_data(self, emcomponent, fname, datas, cur_value):
|
164
|
230
|
emcomponent_fields = emcomponent.fields()
|
165
|
231
|
data_handler = None
|
|
@@ -174,50 +240,71 @@ class DataHandler(MlNamedObject):
|
174
|
240
|
new_val = None
|
175
|
241
|
return self._construct_data(emcomponent, fname, datas, new_val)
|
176
|
242
|
|
|
243
|
+
|
|
244
|
+ ##
|
177
|
245
|
# @brief Designed to be reimplemented by child classes
|
|
246
|
+ #
|
178
|
247
|
# @param emcomponent EmComponent : An EmComponent child class instance
|
179
|
248
|
# @param fname str : The field name
|
180
|
249
|
# @param datas dict : dict storing fields values (from the component)
|
181
|
250
|
# @param cur_value : the value from the current field (identified by fieldname)
|
182
|
251
|
# @return the value
|
183
|
252
|
# @see construct_data() lodel2_dh_check_impl
|
184
|
|
- def _construct_data(self, empcomponent, fname, datas, cur_value):
|
|
253
|
+ def _construct_data(self, emcomponent, fname, datas, cur_value):
|
185
|
254
|
return cur_value
|
186
|
255
|
|
187
|
|
- # @brief Check datas consistency
|
|
256
|
+
|
|
257
|
+ ##
|
|
258
|
+ # @brief Check data consistency
|
188
|
259
|
# @ingroup lodel2_dh_checks
|
189
|
|
- # @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
190
|
|
- # @ref _construct_data() and @ref lodel2_dh_check_impl )
|
191
|
|
- # @warning the datas argument looks like a dict but is not a dict
|
192
|
|
- # see @ref base_classes.DatasConstructor "DatasConstructor" and
|
193
|
|
- # @ref lodel2_dh_datas_construction "Datas construction section"
|
|
260
|
+ #
|
|
261
|
+ # @ref lodel2_dh_datas_construction "Data construction section"
|
194
|
262
|
# @param emcomponent EmComponent : An EmComponent child class instance
|
195
|
263
|
# @param fname : the field name
|
196
|
264
|
# @param datas dict : dict storing fields values
|
197
|
265
|
# @return an Exception instance if fails else True
|
|
266
|
+ #
|
|
267
|
+ # @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
|
|
268
|
+ # @ref _construct_data() and @ref lodel2_dh_check_impl )
|
|
269
|
+ # @warning the data argument looks like a dict but is not a dict
|
|
270
|
+ # see @ref base_classes.DatasConstructor "DatasConstructor" and
|
198
|
271
|
# @todo A implémenter
|
199
|
272
|
def check_data_consistency(self, emcomponent, fname, datas):
|
200
|
273
|
return self._check_data_consistency(emcomponent, fname, datas)
|
201
|
274
|
|
|
275
|
+
|
|
276
|
+ ##
|
202
|
277
|
# @brief Designed to be reimplemented by child classes
|
|
278
|
+ #
|
203
|
279
|
# @param emcomponent EmComponent : An EmComponent child class instance
|
204
|
280
|
# @param fname : the field name
|
205
|
281
|
# @param datas dict : dict storing fields values
|
206
|
282
|
# @return an Exception instance if fails else True
|
|
283
|
+ #
|
207
|
284
|
# @see check_data_consistency() lodel2_dh_check_impl
|
208
|
285
|
def _check_data_consistency(self, emcomponent, fname, datas):
|
209
|
286
|
return True
|
210
|
287
|
|
211
|
|
- # @brief make consistency after a query
|
|
288
|
+
|
|
289
|
+ ##
|
|
290
|
+ # @brief Makes consistency after a query
|
|
291
|
+ #
|
212
|
292
|
# @param emcomponent EmComponent : An EmComponent child class instance
|
213
|
293
|
# @param fname : the field name
|
214
|
294
|
# @param datas dict : dict storing fields values
|
215
|
295
|
# @return an Exception instance if fails else True
|
216
|
|
- # @todo A implémenter
|
|
296
|
+ #
|
|
297
|
+ # @todo To be implemented
|
|
298
|
+ # @remarks It not clear what is the intent of this method...
|
217
|
299
|
def make_consistency(self, emcomponent, fname, datas):
|
218
|
300
|
pass
|
219
|
301
|
|
220
|
|
- # @brief This method is use by plugins to register new data handlers
|
|
302
|
+
|
|
303
|
+ ##
|
|
304
|
+ # @brief Registers a new data handlers
|
|
305
|
+ #
|
|
306
|
+ # @note Used by plugins.
|
|
307
|
+ # @remarks This method is actually never used anywhere. May consider removing it.
|
221
|
308
|
@classmethod
|
222
|
309
|
def register_new_handler(cls, name, data_handler):
|
223
|
310
|
if not inspect.isclass(data_handler):
|
|
@@ -226,7 +313,9 @@ class DataHandler(MlNamedObject):
|
226
|
313
|
raise ValueError("A data handler HAS TO be a child class of DataHandler")
|
227
|
314
|
cls.__custom_handlers[name] = data_handler
|
228
|
315
|
|
229
|
|
- # @brief Load all datahandlers
|
|
316
|
+
|
|
317
|
+ ##
|
|
318
|
+ # @brief Loads all DataHandlers
|
230
|
319
|
@classmethod
|
231
|
320
|
def load_base_handlers(cls):
|
232
|
321
|
if cls._base_handlers is None:
|
|
@@ -239,22 +328,38 @@ class DataHandler(MlNamedObject):
|
239
|
328
|
cls._base_handlers[name.lower()] = obj
|
240
|
329
|
return copy.copy(cls._base_handlers)
|
241
|
330
|
|
|
331
|
+
|
|
332
|
+ ##
|
242
|
333
|
# @brief given a field type name, returns the associated python class
|
243
|
|
- # @param fieldtype_name str : A field type name (not case sensitive)
|
|
334
|
+ #
|
|
335
|
+ # @param name str : A field type name (not case sensitive)
|
244
|
336
|
# @return DataField child class
|
245
|
|
- # @note To access custom data handlers it can be cool to prefix the handler name by plugin
|
246
|
|
- # name for example ? (to ensure name unicity)
|
|
337
|
+ # @throw NameError
|
|
338
|
+ #
|
|
339
|
+ # @note Would not it be better to prefix the DataHandler name with the
|
|
340
|
+ # plugin's one so that it is ensured names are unique?
|
|
341
|
+ # @remarks "do/get what from name?" Consider renaming this method (e.g.
|
|
342
|
+ # 'get_datafield_from_name')
|
247
|
343
|
@classmethod
|
248
|
344
|
def from_name(cls, name):
|
249
|
345
|
cls.load_base_handlers()
|
250
|
346
|
all_handlers = dict(cls._base_handlers, **cls.__custom_handlers)
|
251
|
347
|
name = name.lower()
|
|
348
|
+
|
252
|
349
|
if name not in all_handlers:
|
253
|
350
|
raise NameError("No data handlers named '%s'" % (name,))
|
254
|
351
|
return all_handlers[name]
|
255
|
352
|
|
256
|
|
- # @brief List all datahandlers
|
|
353
|
+
|
|
354
|
+ ##
|
|
355
|
+ # @brief List all DataHandlers
|
257
|
356
|
# @return a dict with, display_name for keys, and a dict for value
|
|
357
|
+ # @remarks ATM, solely used by the EditorialModel.
|
|
358
|
+ # @remarks EditorialModel own class does nothing but calls this class.
|
|
359
|
+ # Moreover, nothing calls it anyway.
|
|
360
|
+ # @remarks It also seems like it is an EM related concern, and has
|
|
361
|
+ # nothing to do with this class. That list appears to be doing
|
|
362
|
+ # a purely presentational job. Isn't that a serialization instead?
|
258
|
363
|
@classmethod
|
259
|
364
|
def list_data_handlers(cls):
|
260
|
365
|
cls.load_base_handlers()
|
|
@@ -269,33 +374,39 @@ class DataHandler(MlNamedObject):
|
269
|
374
|
|
270
|
375
|
return list_dh
|
271
|
376
|
|
272
|
|
- # @brief Return the module name to import in order to use the datahandler
|
273
|
|
- # @param data_handler_name str : Data handler name
|
274
|
|
- # @return a str
|
|
377
|
+
|
|
378
|
+ ##
|
|
379
|
+ # @brief Return the module name to import in order to use the DataHandler
|
|
380
|
+ # @param datahandler_name str : Data handler name
|
|
381
|
+ # @return str
|
|
382
|
+ # @remarks consider renaming this (e.g. "datahandler_module_name")
|
275
|
383
|
@classmethod
|
276
|
|
- def module_name(cls, name):
|
277
|
|
- name = name.lower()
|
278
|
|
- handler_class = cls.from_name(name)
|
|
384
|
+ def module_name(cls, datahandler_name):
|
|
385
|
+ datahandler_name = datahandler_name.lower()
|
|
386
|
+ handler_class = cls.from_name(datahandler_name)
|
279
|
387
|
return '{module_name}.{class_name}'.format(
|
280
|
388
|
module_name=handler_class.__module__,
|
281
|
389
|
class_name=handler_class.__name__
|
282
|
390
|
)
|
283
|
391
|
|
284
|
|
- # @brief __hash__ implementation for fieldtypes
|
|
392
|
+
|
|
393
|
+ ##
|
|
394
|
+ # @brief __hash__ implementation for field types
|
285
|
395
|
def __hash__(self):
|
286
|
396
|
hash_dats = [self.__class__.__module__]
|
287
|
397
|
for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
|
288
|
398
|
hash_dats.append((kdic, getattr(self, kdic)))
|
289
|
399
|
return hash(tuple(hash_dats))
|
290
|
400
|
|
291
|
|
-# @brief Base class for datas data handler (by opposition with references)
|
292
|
|
-# @ingroup lodel2_datahandlers
|
293
|
|
-
|
294
|
401
|
|
|
402
|
+##
|
|
403
|
+# @brief Base class for data data handler (by opposition with references)
|
|
404
|
+# @ingroup lodel2_datahandlers
|
295
|
405
|
class DataField(DataHandler):
|
296
|
406
|
pass
|
297
|
407
|
|
298
|
408
|
|
|
409
|
+##
|
299
|
410
|
# @brief Abstract class for all references
|
300
|
411
|
# @ingroup lodel2_datahandlers
|
301
|
412
|
#
|
|
@@ -303,21 +414,31 @@ class DataField(DataHandler):
|
303
|
414
|
# editorial object
|
304
|
415
|
# @todo Construct data implementation : transform the data into a LeObject instance
|
305
|
416
|
class Reference(DataHandler):
|
|
417
|
+
|
306
|
418
|
base_type = "ref"
|
307
|
|
-
|
308
|
|
- # @brief Instanciation
|
|
419
|
+
|
|
420
|
+ ##
|
|
421
|
+ # @brief Instantiation
|
309
|
422
|
# @param allowed_classes list | None : list of allowed em classes if None no restriction
|
310
|
|
- # @param back_reference tuple | None : tuple containing (LeObject child class, fieldname)
|
311
|
|
- # @param internal bool : if False, the field is not internal
|
|
423
|
+ # @param back_reference tuple | None : tuple containing (LeObject child class, field name)
|
|
424
|
+ # @param internal bool | string: if False, the field is not internal
|
312
|
425
|
# @param **kwargs : other arguments
|
|
426
|
+ # @throw ValueError
|
|
427
|
+ # @remarks internal may hold the string value 'automatic'. So far, nothing
|
|
428
|
+ # mentions what that means, and nothing seems to be aware
|
|
429
|
+ # of an 'automatic' value (at least not in leapi package)
|
313
|
430
|
def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
|
314
|
431
|
self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
|
315
|
|
- # For now usefull to jinja 2
|
|
432
|
+ ##
|
|
433
|
+ # @note what is "useful to Jinja 2"?
|
|
434
|
+ # For now useful to jinja 2
|
316
|
435
|
self.allowed_classes = list() if allowed_classes is None else allowed_classes
|
317
|
436
|
if back_reference is not None:
|
318
|
437
|
if len(back_reference) != 2:
|
319
|
438
|
raise ValueError(
|
320
|
439
|
"A tuple (classname, fieldname) expected but got '%s'" % back_reference)
|
|
440
|
+ ##
|
|
441
|
+ # @note Why is there commented out code? Should it be deleted? Ractivated?
|
321
|
442
|
# if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
|
322
|
443
|
# or not isinstance(back_reference[1], str):
|
323
|
444
|
# raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
|
|
@@ -325,17 +446,24 @@ class Reference(DataHandler):
|
325
|
446
|
self.__back_reference = back_reference
|
326
|
447
|
super().__init__(internal=internal, **kwargs)
|
327
|
448
|
|
|
449
|
+
|
|
450
|
+ ##
|
328
|
451
|
# @brief Method designed to return an empty value for this kind of
|
329
|
452
|
# multipleref
|
|
453
|
+ # @remarks purpose!?
|
330
|
454
|
@classmethod
|
331
|
455
|
def empty(cls):
|
332
|
456
|
return None
|
333
|
457
|
|
|
458
|
+
|
|
459
|
+ ##
|
334
|
460
|
# @brief Property that takes value of a copy of the back_reference tuple
|
335
|
461
|
@property
|
336
|
462
|
def back_reference(self):
|
337
|
463
|
return copy.copy(self.__back_reference)
|
338
|
464
|
|
|
465
|
+
|
|
466
|
+ ##
|
339
|
467
|
# @brief Property that takes value of datahandler of the backreference or
|
340
|
468
|
# None
|
341
|
469
|
@property
|
|
@@ -348,12 +476,17 @@ class Reference(DataHandler):
|
348
|
476
|
def linked_classes(self):
|
349
|
477
|
return copy.copy(self.__allowed_classes)
|
350
|
478
|
|
351
|
|
- # @brief Set the back reference for this field.
|
|
479
|
+
|
|
480
|
+ ##
|
|
481
|
+ # @brief Sets a back reference.
|
352
|
482
|
def _set_back_reference(self, back_reference):
|
353
|
483
|
self.__back_reference = back_reference
|
354
|
484
|
|
355
|
|
- # @brief Check and cast value in appropriate type
|
356
|
|
- # @param value *
|
|
485
|
+
|
|
486
|
+ ##
|
|
487
|
+ # @brief Check and cast value in the appropriate type
|
|
488
|
+ #
|
|
489
|
+ # @param value
|
357
|
490
|
# @throw FieldValidationError if value is an appropriate type
|
358
|
491
|
# @return value
|
359
|
492
|
# @todo implement the check when we have LeObject uid check value
|
|
@@ -369,62 +502,85 @@ class Reference(DataHandler):
|
369
|
502
|
value = uiddh._check_data_value(value)
|
370
|
503
|
else:
|
371
|
504
|
raise FieldValidationError(
|
372
|
|
- "Reference datahandler can not check this value %s if any allowed_class is allowed." % value)
|
|
505
|
+ "Reference datahandler can not check this value %s if any allowed_class is allowed. " % value)
|
373
|
506
|
return value
|
374
|
507
|
|
375
|
|
- # @brief Check datas consistency
|
376
|
|
- # @param emcomponent EmComponent : An EmComponent child class instance
|
377
|
|
- # @param fname : the field name
|
|
508
|
+
|
|
509
|
+ ##
|
|
510
|
+ # @brief Check data consistency
|
|
511
|
+ #
|
|
512
|
+ # @param emcomponent EmComponent :
|
|
513
|
+ # @param fname string : the field name
|
378
|
514
|
# @param datas dict : dict storing fields values
|
379
|
|
- # @return an Exception instance if fails else True
|
380
|
|
- # @todo check for performance issue and check logics
|
381
|
|
- # @warning composed uid capabilities broken here
|
|
515
|
+ # @return bool | Exception :
|
|
516
|
+ #
|
|
517
|
+ # @todo check for performance issues and checks logic
|
|
518
|
+ # @warning composed uid capabilities are broken
|
|
519
|
+ # @remarks Is that really a legitimate case of retuning an Exception object?
|
382
|
520
|
def check_data_consistency(self, emcomponent, fname, datas):
|
383
|
521
|
rep = super().check_data_consistency(emcomponent, fname, datas)
|
384
|
522
|
if isinstance(rep, Exception):
|
385
|
523
|
return rep
|
386
|
524
|
if self.back_reference is None:
|
387
|
525
|
return True
|
388
|
|
- # !! Reimplement instance fetching in construct data !!
|
|
526
|
+ ##
|
|
527
|
+ # @todo Reimplement instance fetching in construct data
|
|
528
|
+ # @remarks Set the previous todo as one, looked like it was intended to be.
|
389
|
529
|
target_class = self.back_reference[0]
|
390
|
530
|
if target_class not in self.__allowed_classes:
|
391
|
531
|
logger.warning('Class of the back_reference given is not an allowed class')
|
392
|
532
|
return False
|
393
|
533
|
value = datas[fname]
|
394
|
|
- target_uidfield = target_class.uid_fieldname()[0] # multi uid broken here
|
|
534
|
+ ##
|
|
535
|
+ # @warning multi uid broken here
|
|
536
|
+ # @remarks Why is that broken? Any clue? Set as a warning.
|
|
537
|
+ target_uidfield = target_class.uid_fieldname()[0]
|
395
|
538
|
obj = target_class.get([(target_uidfield, '=', value)])
|
396
|
539
|
if len(obj) == 0:
|
397
|
540
|
logger.warning('Object referenced does not exist')
|
398
|
541
|
return False
|
399
|
542
|
return True
|
400
|
543
|
|
|
544
|
+
|
|
545
|
+ ##
|
401
|
546
|
# @brief Utility method designed to fetch referenced objects
|
|
547
|
+ #
|
402
|
548
|
# @param value mixed : the field value
|
403
|
549
|
# @throw NotImplementedError
|
|
550
|
+ # @remarks Not implemented? Consider renaming?
|
404
|
551
|
def get_referenced(self, value):
|
405
|
552
|
raise NotImplementedError
|
406
|
553
|
|
407
|
554
|
|
408
|
|
-# @brief This class represent a data_handler for single reference to another object
|
|
555
|
+##
|
|
556
|
+# @brief DataHandler for single reference to another object
|
409
|
557
|
#
|
410
|
|
-# The fields using this data handlers are like "foreign key" on another object
|
|
558
|
+# An instance of this class acts like a "foreign key" to another object
|
411
|
559
|
class SingleRef(Reference):
|
412
|
560
|
|
|
561
|
+
|
413
|
562
|
def __init__(self, allowed_classes=None, **kwargs):
|
414
|
563
|
super().__init__(allowed_classes=allowed_classes, **kwargs)
|
415
|
564
|
|
416
|
|
- # @brief Check and cast value in appropriate type
|
417
|
|
- # @param value: *
|
418
|
|
- # @throw FieldValidationError if value is unappropriate or can not be cast
|
419
|
|
- # @return value
|
|
565
|
+
|
|
566
|
+ ##
|
|
567
|
+ # @brief Checks and casts value to the appropriate type
|
|
568
|
+ #
|
|
569
|
+ # @param value: mixed
|
|
570
|
+ # @throw FieldValidationError if value is inappropriate or can not be cast
|
|
571
|
+ # @return mixed
|
420
|
572
|
def _check_data_value(self, value):
|
421
|
573
|
value = super()._check_data_value(value)
|
422
|
574
|
return value
|
423
|
575
|
|
424
|
|
- # @brief Utility method designed to fetch referenced objects
|
|
576
|
+
|
|
577
|
+ ##
|
|
578
|
+ # @brief Utility method to fetch referenced objects
|
|
579
|
+ #
|
425
|
580
|
# @param value mixed : the field value
|
426
|
581
|
# @return A LeObject child class instance
|
427
|
582
|
# @throw LodelDataHandlerConsistencyException if no referenced object found
|
|
583
|
+ # @remarks Consider renaming (e.g. get_referenced_object)?
|
428
|
584
|
def get_referenced(self, value):
|
429
|
585
|
for leo_cls in self.linked_classes:
|
430
|
586
|
res = leo_cls.get_from_uid(value)
|
|
@@ -434,30 +590,39 @@ class SingleRef(Reference):
|
434
|
590
|
referenced object with uid %s" % value)
|
435
|
591
|
|
436
|
592
|
|
437
|
|
-# @brief This class represent a data_handler for multiple references to another object
|
|
593
|
+##
|
|
594
|
+# @brief DataHandler for multiple references to another object
|
438
|
595
|
# @ingroup lodel2_datahandlers
|
439
|
596
|
#
|
440
|
|
-# The fields using this data handlers are like SingleRef but can store multiple references in one field
|
|
597
|
+# The fields using this data handlers are like SingleRef but can store multiple
|
|
598
|
+# references in one field.
|
441
|
599
|
# @note for the moment split on ',' chars
|
442
|
600
|
class MultipleRef(Reference):
|
443
|
601
|
|
|
602
|
+ ##
|
444
|
603
|
# @brief Constructor
|
445
|
|
- # @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
|
|
604
|
+ #
|
|
605
|
+ # @param max_item int | None : indicate the maximum number of item referenced
|
|
606
|
+ # by this field, None mean no limit
|
446
|
607
|
def __init__(self, max_item=None, **kwargs):
|
447
|
608
|
self.max_item = max_item
|
448
|
609
|
super().__init__(**kwargs)
|
449
|
610
|
|
|
611
|
+ ##
|
450
|
612
|
# @brief Method designed to return an empty value for this kind of
|
451
|
|
- # multipleref
|
|
613
|
+ # multipleref
|
|
614
|
+ # @remarks Purpose!?
|
452
|
615
|
@classmethod
|
453
|
616
|
def empty(cls):
|
454
|
617
|
return []
|
455
|
618
|
|
|
619
|
+
|
|
620
|
+ ##
|
456
|
621
|
# @brief Check and cast value in appropriate type
|
457
|
|
- # @param value *
|
|
622
|
+ # @param value mixed
|
458
|
623
|
# @throw FieldValidationError if value is unappropriate or can not be cast
|
459
|
624
|
# @return value
|
460
|
|
- # @TODO Writing test error for errors when stored multiple references in one field
|
|
625
|
+ # @todo Writing test error for errors when stored multiple references in one field
|
461
|
626
|
def _check_data_value(self, value):
|
462
|
627
|
value = DataHandler._check_data_value(self, value)
|
463
|
628
|
if not hasattr(value, '__iter__'):
|
|
@@ -479,7 +644,9 @@ class MultipleRef(Reference):
|
479
|
644
|
"MultipleRef have for invalid values [%s] :" % (",".join(error_list)))
|
480
|
645
|
return new_val
|
481
|
646
|
|
|
647
|
+ ##
|
482
|
648
|
# @brief Utility method designed to fetch referenced objects
|
|
649
|
+ #
|
483
|
650
|
# @param values mixed : the field values
|
484
|
651
|
# @return A list of LeObject child class instance
|
485
|
652
|
# @throw LodelDataHandlerConsistencyException if some referenced objects
|
|
@@ -502,37 +669,51 @@ class MultipleRef(Reference):
|
502
|
669
|
some referenced objects. Following uids were not found : %s" % ','.join(left))
|
503
|
670
|
|
504
|
671
|
|
505
|
|
-# @brief Class designed to handle datas access will fieldtypes are constructing datas
|
|
672
|
+##
|
|
673
|
+# @brief Class designed to handle data access while field types are constructing data
|
506
|
674
|
# @ingroup lodel2_datahandlers
|
507
|
675
|
#
|
508
|
676
|
# This class is designed to allow automatic scheduling of construct_data calls.
|
509
|
677
|
#
|
510
|
|
-# In theory it's able to detect circular dependencies
|
|
678
|
+# In theory it has the ability to detect circular dependencies
|
511
|
679
|
# @todo test circular deps detection
|
512
|
680
|
# @todo test circular deps false positive
|
|
681
|
+# @remarks Would not it be better to make sure what the code actually is doing?
|
513
|
682
|
class DatasConstructor(object):
|
514
|
683
|
|
|
684
|
+ ##
|
515
|
685
|
# @brief Init a DatasConstructor
|
516
|
|
- # @param leobject LeCrud : @ref LeObject child class
|
|
686
|
+ #
|
|
687
|
+ # @param leobject LeObject
|
517
|
688
|
# @param datas dict : dict with field name as key and field values as value
|
518
|
689
|
# @param fields_handler dict : dict with field name as key and data handler instance as value
|
519
|
690
|
def __init__(self, leobject, datas, fields_handler):
|
520
|
|
- # Stores concerned class
|
521
|
691
|
self._leobject = leobject
|
522
|
|
- # Stores datas and constructed datas
|
523
|
692
|
self._datas = copy.copy(datas)
|
524
|
693
|
# Stores fieldtypes
|
525
|
694
|
self._fields_handler = fields_handler
|
526
|
|
- # Stores list of fieldname for constructed datas
|
|
695
|
+ # Stores list of fieldname for constructed
|
527
|
696
|
self._constructed = []
|
528
|
697
|
# Stores construct calls list
|
529
|
698
|
self._construct_calls = []
|
530
|
699
|
|
|
700
|
+
|
|
701
|
+ ##
|
531
|
702
|
# @brief Implements the dict.keys() method on instance
|
|
703
|
+ #
|
|
704
|
+ # @return list
|
532
|
705
|
def keys(self):
|
533
|
706
|
return self._datas.keys()
|
534
|
707
|
|
535
|
|
- # @brief Allows to access the instance like a dict
|
|
708
|
+
|
|
709
|
+ ##
|
|
710
|
+ # @brief Allows to access the instance like a dict
|
|
711
|
+ #
|
|
712
|
+ # @param fname string: The field name
|
|
713
|
+ # @return field values
|
|
714
|
+ # @throw RuntimeError
|
|
715
|
+ #
|
|
716
|
+ # @note Determine return type
|
536
|
717
|
def __getitem__(self, fname):
|
537
|
718
|
if fname not in self._constructed:
|
538
|
719
|
if fname in self._construct_calls:
|
|
@@ -543,17 +724,25 @@ class DatasConstructor(object):
|
543
|
724
|
self._constructed.append(fname)
|
544
|
725
|
return self._datas[fname]
|
545
|
726
|
|
|
727
|
+
|
|
728
|
+ ##
|
546
|
729
|
# @brief Allows to set instance values like a dict
|
|
730
|
+ #
|
547
|
731
|
# @warning Should not append in theory
|
|
732
|
+ #
|
|
733
|
+ # @remarks Why is a warning issued any time we call this method?
|
548
|
734
|
def __setitem__(self, fname, value):
|
549
|
735
|
self._datas[fname] = value
|
550
|
736
|
warnings.warn("Setting value of an DatasConstructor instance")
|
551
|
737
|
|
552
|
738
|
|
553
|
|
-# @brief Class designed to handle an option of a DataHandler
|
|
739
|
+##
|
|
740
|
+# @brief Class designed to handle a DataHandler option
|
554
|
741
|
class DatahandlerOption(MlNamedObject):
|
555
|
742
|
|
556
|
|
- # @brief instanciates a new Datahandler option object
|
|
743
|
+
|
|
744
|
+ ##
|
|
745
|
+ # @brief instantiates a new DataHandlerOption object
|
557
|
746
|
#
|
558
|
747
|
# @param id str
|
559
|
748
|
# @param display_name MlString
|
|
@@ -564,13 +753,18 @@ class DatahandlerOption(MlNamedObject):
|
564
|
753
|
self.__validator = validator
|
565
|
754
|
super().__init__(display_name, help_text)
|
566
|
755
|
|
|
756
|
+ ##
|
|
757
|
+ # @brief Accessor to the id property.
|
567
|
758
|
@property
|
568
|
759
|
def id(self):
|
569
|
760
|
return self.__id
|
570
|
761
|
|
|
762
|
+ ##
|
571
|
763
|
# @brief checks a value corresponding to this option is valid
|
572
|
|
- # @param value
|
573
|
|
- # @return casted value
|
|
764
|
+ #
|
|
765
|
+ # @param value mixed
|
|
766
|
+ # @return cast value
|
|
767
|
+ # @throw ValueError
|
574
|
768
|
def check_value(self, value):
|
575
|
769
|
try:
|
576
|
770
|
return self.__validator(value)
|