Browse Source

Merge branch 'lodel2-datahandlers'

prieto 7 years ago
parent
commit
5b09492048
39 changed files with 1175 additions and 1038 deletions
  1. 2
    0
      configure.ac
  2. 5
    5
      editorial_models/em_simple.py
  3. 1
    1
      lodel/Makefile.am
  4. 139
    128
      lodel/editorial_model/components.py
  5. 70
    66
      lodel/editorial_model/model.py
  6. 226
    140
      lodel/leapi/datahandlers/base_classes.py
  7. 10
    11
      lodel/leapi/datahandlers/datas.py
  8. 38
    34
      lodel/leapi/datahandlers/datas_base.py
  9. 3
    2
      lodel/leapi/datahandlers/exceptions.py
  10. 39
    34
      lodel/leapi/datahandlers/references.py
  11. 163
    160
      lodel/leapi/leobject.py
  12. 2
    0
      lodel/mlnamedobject/Makefile.am
  13. 0
    0
      lodel/mlnamedobject/__init__.py
  14. 18
    0
      lodel/mlnamedobject/mlnamedobject.py
  15. 6
    6
      lodel/plugin/datasource_plugin.py
  16. 2
    2
      lodel/plugin/extensions.py
  17. 2
    2
      lodel/plugin/interface.py
  18. 2
    2
      lodel/plugin/sessionhandler.py
  19. 1
    1
      lodel/plugins/dummy/__init__.py
  20. 2
    2
      lodel/plugins/dummy/confspec.py
  21. 2
    2
      lodel/plugins/dummy_datasource/__init__.py
  22. 1
    1
      lodel/plugins/filesystem_session/__init__.py
  23. 4
    4
      lodel/plugins/filesystem_session/confspec.py
  24. 7
    7
      lodel/plugins/mongodb_datasource/confspec.py
  25. 3
    3
      lodel/plugins/multisite/__init__.py
  26. 15
    15
      lodel/plugins/multisite/confspecs.py
  27. 3
    3
      lodel/plugins/ram_sessions/__init__.py
  28. 13
    13
      lodel/plugins/webui/confspec.py
  29. 8
    8
      lodel/plugins/webui/templates/listing/issue.html
  30. 80
    68
      lodel/settings/settings.py
  31. 52
    60
      lodel/settings/settings_loader.py
  32. 1
    1
      lodel/utils/datetime.py
  33. 17
    16
      lodel/utils/mlstring.py
  34. 2
    0
      lodel/validator/Makefile.am
  35. 6
    0
      lodel/validator/__init__.py
  36. 202
    215
      lodel/validator/validator.py
  37. 12
    10
      nocontext_tests.py
  38. 2
    2
      scripts/settings_validator.py
  39. 14
    14
      tests/settings/test_validator.py

+ 2
- 0
configure.ac View File

@@ -12,6 +12,8 @@ AC_CONFIG_FILES([Makefile \
12 12
 		lodel/leapi/datahandlers/Makefile \
13 13
 	lodel/plugin/Makefile \
14 14
 	lodel/settings/Makefile \
15
+	lodel/validator/Makefile \
16
+	lodel/mlnamedobject/Makefile \
15 17
 	lodel/utils/Makefile \
16 18
 	progs/Makefile \
17 19
 	progs/slim/Makefile \

+ 5
- 5
editorial_models/em_simple.py View File

@@ -272,7 +272,7 @@ article.new_field(  'author_note',
272 272
                     group = editorial_group,
273 273
                     data_handler = 'text'
274 274
 )
275
-# Classe Review 
275
+# Classe Review
276 276
 review = em.new_class( 'review',
277 277
                         display_name = 'Review',
278 278
                         group = editorial_group,
@@ -439,7 +439,7 @@ issue.new_field(    'print_pub_date',
439 439
                     },
440 440
                   data_handler = 'datetime',
441 441
                   group = editorial_group,
442
-)     
442
+)
443 443
 issue.new_field(    'e_pub_date',
444 444
                     display_name = {
445 445
                         'eng': 'Electronic publication date',
@@ -447,7 +447,7 @@ issue.new_field(    'e_pub_date',
447 447
                     },
448 448
                   data_handler = 'datetime',
449 449
                   group = editorial_group,
450
-)  
450
+)
451 451
 issue.new_field(    'abstract',
452 452
                     display_name = {
453 453
                         'eng': 'Abstract',
@@ -455,7 +455,7 @@ issue.new_field(    'abstract',
455 455
                     },
456 456
                   data_handler = 'text',
457 457
                   group = editorial_group,
458
-) 
458
+)
459 459
 issue.new_field(    'collection',
460 460
                     display_name = {
461 461
                         'eng': 'Collection',
@@ -691,7 +691,7 @@ user.new_field(
691 691
     group = user_group, data_handler = 'password', internal = False)
692 692
 
693 693
 
694
-#em.save('xmlfile', filename = 'examples/em_test.xml')
694
+em.save('xmlfile', filename = 'editorial_models/em_simple.xml')
695 695
 pickle_file = 'examples/em_simple.pickle'
696 696
 em.save('picklefile', filename = pickle_file)
697 697
 print("Output written in %s" % pickle_file)

+ 1
- 1
lodel/Makefile.am View File

@@ -1,4 +1,4 @@
1
-SUBDIRS=auth editorial_model leapi plugin settings utils plugins
1
+SUBDIRS=auth editorial_model leapi plugin settings utils plugins validator mlnamedobject
2 2
 EXTRA_DIST = plugins
3 3
 lodel_PYTHON = *.py
4 4
 CLEANFILES = buildconf.py

+ 139
- 128
lodel/editorial_model/components.py View File

@@ -1,6 +1,6 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3
-##@package lodel.editorial_model.components
3
+# @package lodel.editorial_model.components
4 4
 #@brief Defines all @ref lodel2_em "EM" components
5 5
 #@ingroup lodel2_em
6 6
 
@@ -12,28 +12,30 @@ import hashlib
12 12
 from lodel.context import LodelContext
13 13
 LodelContext.expose_modules(globals(), {
14 14
     'lodel.utils.mlstring': ['MlString'],
15
+    'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
15 16
     'lodel.settings': ['Settings'],
16 17
     'lodel.editorial_model.exceptions': ['EditorialModelError', 'assert_edit'],
17 18
     'lodel.leapi.leobject': ['CLASS_ID_FIELDNAME']})
18 19
 
19
-##@brief Abstract class to represent editorial model components
20
+# @brief Abstract class to represent editorial model components
20 21
 # @see EmClass EmField
21 22
 # @todo forbid '.' in uid
22 23
 #@ingroup lodel2_em
23
-class EmComponent(object):
24
-    
25
-    ##@brief Instanciate an EmComponent
24
+
25
+
26
+class EmComponent(MlNamedObject):
27
+
28
+    # @brief Instanciate an EmComponent
26 29
     # @param uid str : uniq identifier
27 30
     # @param display_name MlString|str|dict : component display_name
28 31
     # @param help_text MlString|str|dict : help_text
29
-    def __init__(self, uid, display_name = None, help_text = None, group = None):
32
+    def __init__(self, uid, display_name=None, help_text=None, group=None):
30 33
         if self.__class__ == EmComponent:
31 34
             raise NotImplementedError('EmComponent is an abstract class')
32 35
         self.uid = uid
33
-        self.display_name = None if display_name is None else MlString(display_name)
34
-        self.help_text = None if help_text is None else MlString(help_text)
35 36
         self.group = group
36
-    
37
+        super().__init__(display_name, help_text)
38
+
37 39
     def __str__(self):
38 40
         if self.display_name is None:
39 41
             return str(self.uid)
@@ -42,34 +44,34 @@ class EmComponent(object):
42 44
     def d_hash(self):
43 45
         m = hashlib.md5()
44 46
         for data in (
45
-                        self.uid,
46
-                        'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
47
-                        'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
48
-                        'NOGROUP' if self.group is None else str(self.group.d_hash()),
47
+            self.uid,
48
+            'NODISPNAME' if self.display_name is None else str(self.display_name.d_hash()),
49
+            'NOHELP' if self.help_text is None else str(self.help_text.d_hash()),
50
+            'NOGROUP' if self.group is None else str(self.group.d_hash()),
49 51
         ):
50 52
             m.update(bytes(data, 'utf-8'))
51 53
         return int.from_bytes(m.digest(), byteorder='big')
52 54
 
53 55
 
54
-##@brief Handles editorial model objects classes
56
+# @brief Handles editorial model objects classes
55 57
 #@ingroup lodel2_em
56 58
 class EmClass(EmComponent):
57
-    
58
-    ##@brief Instanciate a new EmClass
59
+
60
+    # @brief Instanciate a new EmClass
59 61
     #@param uid str : uniq identifier
60 62
     #@param display_name MlString|str|dict : component display_name
61 63
     #@param abstract bool : set the class as asbtract if True
62 64
     #@param pure_abstract bool : if True the EmClass will not be represented in
63
-    #leapi dyncode
65
+    # leapi dyncode
64 66
     #@param parents list: parent EmClass list or uid list
65 67
     #@param help_text MlString|str|dict : help_text
66
-    #@param datasources str|tuple|list : The datasource name ( see 
68
+    #@param datasources str|tuple|list : The datasource name ( see
67 69
     #@ref lodel2_datasources ) or two names (first is read_only datasource the
68
-    #second is read write)
70
+    # second is read write)
69 71
     def __init__(
70
-        self, uid, display_name = None, help_text = None, abstract = False,
71
-        parents = None, group = None, pure_abstract = False,
72
-        datasources = 'default'):
72
+            self, uid, display_name=None, help_text=None, abstract=False,
73
+            parents=None, group=None, pure_abstract=False,
74
+            datasources='default'):
73 75
 
74 76
         super().__init__(uid, display_name, help_text, group)
75 77
         self.abstract = bool(abstract)
@@ -85,49 +87,50 @@ class EmClass(EmComponent):
85 87
                 parents = [parents]
86 88
             for parent in parents:
87 89
                 if not isinstance(parent, EmClass):
88
-                    raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
90
+                    raise ValueError(
91
+                        "<class EmClass> expected in parents list, but %s found" % type(parent))
89 92
         else:
90 93
             parents = list()
91 94
         self.parents = parents
92
-        ##@brief Stores EmFields instances indexed by field uid
93
-        self.__fields = dict() 
94
-        
95
+        # @brief Stores EmFields instances indexed by field uid
96
+        self.__fields = dict()
97
+
95 98
         self.group = group
96 99
         if group is None:
97 100
             warnings.warn("NO GROUP FOR EMCLASS %s" % uid)
98 101
         else:
99 102
             group.add_components([self])
100
-    
101
-        #Adding common field
103
+
104
+        # Adding common field
102 105
         if not self.abstract:
103 106
             self.new_field(
104 107
                 CLASS_ID_FIELDNAME,
105
-                display_name = {
108
+                display_name={
106 109
                     'eng': "LeObject subclass identifier",
107 110
                     'fre': "Identifiant de la class fille de LeObject"},
108
-                help_text = {
111
+                help_text={
109 112
                     'eng': "Allow to create instance of the good class when\
110 113
  fetching arbitrary datas from DB"},
111
-                data_handler = 'LeobjectSubclassIdentifier',
112
-                internal = True,
113
-                group = group)
114
+                data_handler='LeobjectSubclassIdentifier',
115
+                internal=True,
116
+                group=group)
114 117
 
115
-    ##@brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
118
+    # @brief Property that represent a dict of all fields (the EmField defined in this class and all its parents)
116 119
     # @todo use Settings.editorialmodel.groups to determine wich fields should be returned
117 120
     @property
118 121
     def __all_fields(self):
119 122
         res = dict()
120
-        for pfields in [ p.__all_fields for p in self.parents]:
123
+        for pfields in [p.__all_fields for p in self.parents]:
121 124
             res.update(pfields)
122 125
         res.update(self.__fields)
123 126
         return res
124
-    
125
-    ##@brief RO access to datasource attribute
127
+
128
+    # @brief RO access to datasource attribute
126 129
     @property
127 130
     def datasource(self):
128 131
         return self.__datasource
129 132
 
130
-    ##@brief Return the list of all dependencies
133
+    # @brief Return the list of all dependencies
131 134
     #
132 135
     # Reccursive parents listing
133 136
     @property
@@ -140,29 +143,29 @@ class EmClass(EmComponent):
140 143
             res |= parent.parents_recc
141 144
         return res
142 145
 
143
-    ##@brief EmField getter
146
+    # @brief EmField getter
144 147
     # @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
145 148
     # @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
146 149
     # @return A list on EmFields instances (if uid is None) else return an EmField instance
147 150
     # @todo use Settings.editorialmodel.groups to determine wich fields should be returned
148
-    def fields(self, uid = None, no_parents = False):
151
+    def fields(self, uid=None, no_parents=False):
149 152
         fields = self.__fields if no_parents else self.__all_fields
150 153
         try:
151 154
             return list(fields.values()) if uid is None else fields[uid]
152 155
         except KeyError:
153 156
             raise EditorialModelError("No such EmField '%s'" % uid)
154
-    
155
-    ##@brief Keep in __fields only fields contained in active groups
157
+
158
+    # @brief Keep in __fields only fields contained in active groups
156 159
     def _set_active_fields(self, active_groups):
157 160
         if not Settings.editorialmodel.editormode:
158 161
             active_fields = []
159 162
             for grp_name, agrp in active_groups.items():
160
-                active_fields += [ emc for emc in agrp.components()
161
-                    if isinstance(emc, EmField)]
162
-            self.__fields = { fname:fdh for fname, fdh in self.__fields.items()
163
-                if fdh in active_fields }
163
+                active_fields += [emc for emc in agrp.components()
164
+                                  if isinstance(emc, EmField)]
165
+            self.__fields = {fname: fdh for fname, fdh in self.__fields.items()
166
+                             if fdh in active_fields}
164 167
 
165
-    ##@brief Add a field to the EmClass
168
+    # @brief Add a field to the EmClass
166 169
     # @param emfield EmField : an EmField instance
167 170
     # @warning do not add an EmField allready in another class !
168 171
     # @throw EditorialModelException if an EmField with same uid allready in this EmClass (overwritting allowed from parents)
@@ -170,38 +173,40 @@ class EmClass(EmComponent):
170 173
     def add_field(self, emfield):
171 174
         assert_edit()
172 175
         if emfield.uid in self.__fields:
173
-            raise EditorialModelError("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
176
+            raise EditorialModelError(
177
+                "Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
174 178
         # Incomplete field override check
175 179
         if emfield.uid in self.__all_fields:
176 180
             parent_field = self.__all_fields[emfield.uid]
177 181
             if not emfield.data_handler_instance.can_override(parent_field.data_handler_instance):
178
-                raise AttributeError("'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
182
+                raise AttributeError(
183
+                    "'%s' field override a parent field, but data_handles are not compatible" % emfield.uid)
179 184
         self.__fields[emfield.uid] = emfield
180 185
         return emfield
181
-    
182
-    ##@brief Create a new EmField and add it to the EmClass
186
+
187
+    # @brief Create a new EmField and add it to the EmClass
183 188
     # @param data_handler str : A DataHandler name
184 189
     # @param uid str : the EmField uniq id
185
-    # @param **field_kwargs :  EmField constructor parameters ( see @ref EmField.__init__() ) 
190
+    # @param **field_kwargs :  EmField constructor parameters ( see @ref EmField.__init__() )
186 191
     def new_field(self, uid, data_handler, **field_kwargs):
187 192
         assert_edit()
188 193
         return self.add_field(EmField(uid, data_handler, self, **field_kwargs))
189 194
 
190 195
     def d_hash(self):
191 196
         m = hashlib.md5()
192
-        payload = str(super().d_hash())  + ("1" if self.abstract else "0") 
197
+        payload = str(super().d_hash()) + ("1" if self.abstract else "0")
193 198
 
194 199
         for p in sorted(self.parents):
195 200
             payload += str(p.d_hash())
196 201
         for fuid in sorted(self.__fields.keys()):
197 202
             payload += str(self.__fields[fuid].d_hash())
198
-            
203
+
199 204
         m.update(bytes(payload, 'utf-8'))
200 205
         return int.from_bytes(m.digest(), byteorder='big')
201 206
 
202 207
     def __str__(self):
203 208
         return "<class EmClass %s>" % self.uid
204
-    
209
+
205 210
     def __repr__(self):
206 211
         if not self.abstract:
207 212
             abstract = ''
@@ -209,94 +214,95 @@ class EmClass(EmComponent):
209 214
             abstract = 'PureAbstract'
210 215
         else:
211 216
             abstract = 'Abstract'
212
-        return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid) )
217
+        return "<class %s EmClass uid=%s>" % (abstract, repr(self.uid))
213 218
 
214 219
 
215
-##@brief Handles editorial model classes fields
220
+# @brief Handles editorial model classes fields
216 221
 #@ingroup lodel2_em
217 222
 class EmField(EmComponent):
218 223
 
219
-    ##@brief Instanciate a new EmField
224
+    # @brief Instanciate a new EmField
220 225
     # @param uid str : uniq identifier
221 226
     # @param display_name MlString|str|dict : field display_name
222 227
     # @param data_handler str : A DataHandler name
223 228
     # @param help_text MlString|str|dict : help text
224 229
     # @param group EmGroup :
225 230
     # @param **handler_kwargs : data handler arguments
226
-    def __init__(self, uid, data_handler, em_class = None, display_name = None, help_text = None, group = None, **handler_kwargs):
231
+    def __init__(self, uid, data_handler, em_class=None, display_name=None, help_text=None, group=None, **handler_kwargs):
227 232
         from lodel.leapi.datahandlers.base_classes import DataHandler
228 233
         super().__init__(uid, display_name, help_text, group)
229
-        ##@brief The data handler name
234
+        # @brief The data handler name
230 235
         self.data_handler_name = data_handler
231
-        ##@brief The data handler class
236
+        # @brief The data handler class
232 237
         self.data_handler_cls = DataHandler.from_name(data_handler)
233
-        ##@brief The data handler instance associated with this EmField
238
+        # @brief The data handler instance associated with this EmField
234 239
         self.data_handler_instance = self.data_handler_cls(**handler_kwargs)
235
-        ##@brief Stores data handler instanciation options
240
+        # @brief Stores data handler instanciation options
236 241
         self.data_handler_options = handler_kwargs
237
-        ##@brief Stores the emclass that contains this field (set by EmClass.add_field() method)
242
+        # @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
238 243
         self._emclass = em_class
239 244
         if self._emclass is None:
240
-            warnings.warn("No EmClass for field %s" %uid)
245
+            warnings.warn("No EmClass for field %s" % uid)
241 246
         if group is None:
242 247
             warnings.warn("No EmGroup for field  %s" % uid)
243 248
         else:
244 249
             group.add_components([self])
245 250
 
246
-    ##@brief Returns data_handler_name attribute
251
+    # @brief Returns data_handler_name attribute
247 252
     def get_data_handler_name(self):
248 253
         return copy.copy(self.data_handler_name)
249
-        
250
-    ##@brief Returns data_handler_cls attribute
254
+
255
+    # @brief Returns data_handler_cls attribute
251 256
     def get_data_handler_cls(self):
252
-        return copy.copy(selfdata_handler_cls)
253
-    
257
+        return copy.copy(self.data_handler_cls)
258
+
254 259
     ##@brief Returne the uid of the emclass which contains this field
255 260
     def get_emclass_uid(self):
256 261
         return self._emclass.uid
257
-    
262
+
258 263
     # @warning Not complete !
259 264
     # @todo Complete the hash when data handlers becomes available
260 265
     def d_hash(self):
261 266
         return int.from_bytes(hashlib.md5(
262
-                        bytes(
263
-                                "%s%s%s" % (  super().d_hash(),
264
-                                            self.data_handler_name,
265
-                                            self.data_handler_options), 
266
-                                'utf-8')
267
+            bytes(
268
+                "%s%s%s" % (super().d_hash(),
269
+                            self.data_handler_name,
270
+                            self.data_handler_options),
271
+                'utf-8')
267 272
         ).digest(), byteorder='big')
268 273
 
269
-##@brief Handles functionnal group of EmComponents
274
+# @brief Handles functionnal group of EmComponents
270 275
 #@ingroup lodel2_em
271
-class EmGroup(object):
272
-        
273
-    ##@brief Create a new EmGroup
276
+
277
+
278
+class EmGroup(MlNamedObject):
279
+
280
+    # @brief Create a new EmGroup
274 281
     # @note you should NEVER call the constructor yourself. Use Model.add_group instead
275 282
     # @param uid str : Uniq identifier
276 283
     # @param depends list : A list of EmGroup dependencies
277
-    # @param display_name MlString|str : 
278
-    # @param help_text MlString|str : 
279
-    def __init__(self, uid, depends = None, display_name = None, help_text = None):
284
+    # @param display_name MlString|str :
285
+    # @param help_text MlString|str :
286
+    def __init__(self, uid, depends=None, display_name=None, help_text=None):
280 287
         self.uid = uid
281
-        ##@brief Stores the list of groups that depends on this EmGroup indexed by uid
288
+        # @brief Stores the list of groups that depends on this EmGroup indexed by uid
282 289
         self.required_by = dict()
283
-        ##@brief Stores the list of dependencies (EmGroup) indexed by uid
290
+        # @brief Stores the list of dependencies (EmGroup) indexed by uid
284 291
         self.require = dict()
285
-        ##@brief Stores the list of EmComponent instances contained in this group
292
+        # @brief Stores the list of EmComponent instances contained in this group
286 293
         self.__components = set()
294
+        super().__init__(display_name, help_text)
287 295
 
288
-        self.display_name = None if display_name is None else MlString(display_name)
289
-        self.help_text = None if help_text is None else MlString(help_text)
290 296
         if depends is not None:
291 297
             for grp in depends:
292 298
                 if not isinstance(grp, EmGroup):
293 299
                     raise ValueError("EmGroup expected in depends argument but %s found" % grp)
294 300
                 self.add_dependencie(grp)
295
-    
296
-    ##@brief Returns EmGroup dependencie
301
+
302
+    # @brief Returns EmGroup dependencie
297 303
     # @param recursive bool : if True return all dependencies and their dependencies
298 304
     # @return a dict of EmGroup identified by uid
299
-    def dependencies(self, recursive = False):
305
+    def dependencies(self, recursive=False):
300 306
         res = copy.copy(self.require)
301 307
         if not recursive:
302 308
             return res
@@ -308,11 +314,11 @@ class EmGroup(object):
308 314
                     to_scan.append(new_dep)
309 315
                     res[new_dep.uid] = new_dep
310 316
         return res
311
-    
312
-    ##@brief Returns EmGroup applicants
317
+
318
+    # @brief Returns EmGroup applicants
313 319
     # @param recursive bool : if True return all dependencies and their dependencies
314 320
     # @returns a dict of EmGroup identified by uid
315
-    def applicants(self, recursive = False):
321
+    def applicants(self, recursive=False):
316 322
         res = copy.copy(self.required_by)
317 323
         if not recursive:
318 324
             return res
@@ -324,29 +330,31 @@ class EmGroup(object):
324 330
                     to_scan.append(new_app)
325 331
                     res[new_app.uid] = new_app
326 332
         return res
327
-    
328
-    ##@brief Returns EmGroup components
333
+
334
+    # @brief Returns EmGroup components
329 335
     # @returns a copy of the set of components
330 336
     def components(self):
331 337
         return (self.__components).copy()
332 338
 
333
-    ##@brief Returns EmGroup display_name
339
+    # @brief Returns EmGroup display_name
334 340
     #  @param lang str | None : If None return default lang translation
335 341
     #  @returns None if display_name is None, a str for display_name else
336 342
     def get_display_name(self, lang=None):
337
-        name=self.display_name
338
-        if name is None : return None
339
-        return name.get(lang);
343
+        name = self.display_name
344
+        if name is None:
345
+            return None
346
+        return name.get(lang)
340 347
 
341
-    ##@brief Returns EmGroup help_text
348
+    # @brief Returns EmGroup help_text
342 349
     #  @param lang str | None : If None return default lang translation
343 350
     #  @returns None if display_name is None, a str for display_name else
344 351
     def get_help_text(self, lang=None):
345
-        help=self.help_text
346
-        if help is None : return None
347
-        return help.get(lang);
348
-    
349
-    ##@brief Add components in a group
352
+        help = self.help_text
353
+        if help is None:
354
+            return None
355
+        return help.get(lang)
356
+
357
+    # @brief Add components in a group
350 358
     # @param components list : EmComponent instances list
351 359
     def add_components(self, components):
352 360
         assert_edit()
@@ -357,10 +365,11 @@ class EmGroup(object):
357 365
                     msg %= (component, self)
358 366
                     warnings.warn(msg)
359 367
             elif not isinstance(component, EmClass):
360
-                raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
368
+                raise EditorialModelError(
369
+                    "Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
361 370
         self.__components |= set(components)
362 371
 
363
-    ##@brief Add a dependencie
372
+    # @brief Add a dependencie
364 373
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
365 374
     def add_dependencie(self, grp):
366 375
         assert_edit()
@@ -368,16 +377,17 @@ class EmGroup(object):
368 377
             for group in grp:
369 378
                 self.add_dependencie(group)
370 379
             return
371
-        except TypeError: pass
372
-                
380
+        except TypeError:
381
+            pass
382
+
373 383
         if grp.uid in self.require:
374 384
             return
375 385
         if self.__circular_dependencie(grp):
376 386
             raise EditorialModelError("Circular dependencie detected, cannot add dependencie")
377 387
         self.require[grp.uid] = grp
378 388
         grp.required_by[self.uid] = self
379
-        
380
-    ##@brief Add a applicant
389
+
390
+    # @brief Add a applicant
381 391
     # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
382 392
     # Useless ???
383 393
     def add_applicant(self, grp):
@@ -386,26 +396,27 @@ class EmGroup(object):
386 396
             for group in grp:
387 397
                 self.add_applicant(group)
388 398
             return
389
-        except TypeError: pass
390
-                
399
+        except TypeError:
400
+            pass
401
+
391 402
         if grp.uid in self.required_by:
392 403
             return
393 404
         if self.__circular_applicant(grp):
394 405
             raise EditorialModelError("Circular applicant detected, cannot add applicant")
395 406
         self.required_by[grp.uid] = grp
396 407
         grp.require[self.uid] = self
397
-    
398
-    ##@brief Search for circular dependencie
408
+
409
+    # @brief Search for circular dependencie
399 410
     # @return True if circular dep found else False
400 411
     def __circular_dependencie(self, new_dep):
401 412
         return self.uid in new_dep.dependencies(True)
402
-    
403
-    ##@brief Search for circular applicant
413
+
414
+    # @brief Search for circular applicant
404 415
     # @return True if circular app found else False
405 416
     def __circular_applicant(self, new_app):
406 417
         return self.uid in new_app.applicants(True)
407 418
 
408
-    ##@brief Fancy string representation of an EmGroup
419
+    # @brief Fancy string representation of an EmGroup
409 420
     # @return a string
410 421
     def __str__(self):
411 422
         if self.display_name is None:
@@ -414,11 +425,11 @@ class EmGroup(object):
414 425
             return self.display_name.get()
415 426
 
416 427
     def d_hash(self):
417
-        
428
+
418 429
         payload = "%s%s%s" % (
419
-                                self.uid,
420
-                                'NODNAME' if self.display_name is None else self.display_name.d_hash(),
421
-                                'NOHELP' if self.help_text is None else self.help_text.d_hash()
430
+            self.uid,
431
+            'NODNAME' if self.display_name is None else self.display_name.d_hash(),
432
+            'NOHELP' if self.help_text is None else self.help_text.d_hash()
422 433
         )
423 434
         for recurs in (False, True):
424 435
             deps = self.dependencies(recurs)
@@ -427,11 +438,11 @@ class EmGroup(object):
427 438
         for req_by_uid in self.required_by:
428 439
             payload += req_by_uid
429 440
         return int.from_bytes(
430
-                                bytes(payload, 'utf-8'),
431
-                                byteorder = 'big'
441
+            bytes(payload, 'utf-8'),
442
+            byteorder='big'
432 443
         )
433
-    
434
-    ##@brief Complete string representation of an EmGroup
444
+
445
+    # @brief Complete string representation of an EmGroup
435 446
     # @return a string
436 447
     def __repr__(self):
437
-        return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]) )
448
+        return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]))

+ 70
- 66
lodel/editorial_model/model.py View File

@@ -7,6 +7,7 @@ import copy
7 7
 from lodel.context import LodelContext
8 8
 LodelContext.expose_modules(globals(), {
9 9
     'lodel.utils.mlstring': ['MlString'],
10
+    'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
10 11
     'lodel.logger': 'logger',
11 12
     'lodel.settings': ['Settings'],
12 13
     'lodel.settings.utils': ['SettingsError'],
@@ -14,29 +15,34 @@ LodelContext.expose_modules(globals(), {
14 15
     'lodel.editorial_model.components': ['EmClass', 'EmField', 'EmGroup']})
15 16
 
16 17
 
17
-##@brief Describe an editorial model
18
+# @brief Describe an editorial model
18 19
 #@ingroup lodel2_em
19
-class EditorialModel(object):
20
-    
21
-    ##@brief Create a new editorial model
20
+class EditorialModel(MlNamedObject):
21
+
22
+    # @brief Create a new editorial model
22 23
     # @param name MlString|str|dict : the editorial model name
23 24
     # @param description MlString|str|dict : the editorial model description
24
-    def __init__(self, name, description = None):
25
+    def __init__(self, name, description=None, display_name=None, help_text=None):
25 26
         self.name = MlString(name)
26 27
         self.description = MlString(description)
27
-        ##@brief Stores all groups indexed by id
28
+        # @brief Stores all groups indexed by id
28 29
         self.__groups = dict()
29
-        ##@brief Stores all classes indexed by id
30
+        # @brief Stores all classes indexed by id
30 31
         self.__classes = dict()
31
-        ## @brief Stores all activated groups indexed by id
32
+        #  @brief Stores all activated groups indexed by id
32 33
         self.__active_groups = dict()
33
-        ## @brief Stores all activated classes indexed by id
34
+        #  @brief Stores all activated classes indexed by id
34 35
         self.__active_classes = dict()
35 36
         self.__set_actives()
36
-    
37
-    ##@brief EmClass uids accessor
37
+        if display_name is None:
38
+            display_name = name
39
+        if help_text is None:
40
+            help_text = description
41
+        super().__init__(display_name, help_text)
42
+
43
+    # @brief EmClass uids accessor
38 44
     #@return a dict of emclasses
39
-    def all_classes(self, uid = None):
45
+    def all_classes(self, uid=None):
40 46
         if uid is None:
41 47
             return copy.copy(self.__classes)
42 48
         else:
@@ -44,8 +50,8 @@ class EditorialModel(object):
44 50
                 return copy.copy(self.__classes[uid])
45 51
             except KeyError:
46 52
                 raise EditorialModelException("EmClass not found : '%s'" % uid)
47
-                
48
-    def all_classes_ref(self, uid = None):
53
+
54
+    def all_classes_ref(self, uid=None):
49 55
         if uid is None:
50 56
             return self.__classes
51 57
         else:
@@ -53,16 +59,15 @@ class EditorialModel(object):
53 59
                 return self.__classes[uid]
54 60
             except KeyError:
55 61
                 raise EditorialModelException("EmGroup not found : '%s'" % uid)
56
-                                
57
-    ##@brief active EmClass uids accessor
62
+
63
+    # @brief active EmClass uids accessor
58 64
     #@return a list of class uids
59 65
     def active_classes_uids(self):
60
-            return list(self.__active_classes.keys())
61
-        
62
-    
63
-    ##@brief EmGroups accessor
66
+        return list(self.__active_classes.keys())
67
+
68
+    # @brief EmGroups accessor
64 69
     #@return a dict of groups
65
-    def all_groups(self, uid = None):
70
+    def all_groups(self, uid=None):
66 71
         if uid is None:
67 72
             return copy.copy(self.__groups)
68 73
         else:
@@ -70,10 +75,10 @@ class EditorialModel(object):
70 75
                 return copy.copy(self.__groups[uid])
71 76
             except KeyError:
72 77
                 raise EditorialModelException("EmGroup not found : '%s'" % uid)
73
-    
74
-    ##@brief EmGroups accessor
78
+
79
+    # @brief EmGroups accessor
75 80
     #@return a dict of groups
76
-    def all_groups_ref(self, uid = None):
81
+    def all_groups_ref(self, uid=None):
77 82
         if uid is None:
78 83
             return self.__groups
79 84
         else:
@@ -81,26 +86,26 @@ class EditorialModel(object):
81 86
                 return self.__groups[uid]
82 87
             except KeyError:
83 88
                 raise EditorialModelException("EmGroup not found : '%s'" % uid)
84
-                
85
-    ##@brief active EmClass uids accessor
89
+
90
+    # @brief active EmClass uids accessor
86 91
     #@return a list of class uids
87 92
     def active_groups_uids(self):
88
-            return list(self.__active_groups.keys())
93
+        return list(self.__active_groups.keys())
89 94
 
90
-    ##@brief EmClass accessor
95
+    # @brief EmClass accessor
91 96
     #@param uid None | str : give this argument to get a specific EmClass
92 97
     #@return if uid is given returns an EmClass else returns an EmClass
93 98
     # iterator
94 99
     #@todo use Settings.editorialmodel.groups to determine wich classes should
95 100
     # be returned
96
-    def classes(self, uid = None):
101
+    def classes(self, uid=None):
97 102
         try:
98
-            return self.__elt_getter(   self.__active_classes,
99
-                                        uid)
103
+            return self.__elt_getter(self.__active_classes,
104
+                                     uid)
100 105
         except KeyError:
101 106
             raise EditorialModelException("EmClass not found : '%s'" % uid)
102
-    
103
-    ##@brief EmClass child list accessor
107
+
108
+    # @brief EmClass child list accessor
104 109
     #@param uid str : the EmClass uid
105 110
     #@return a set of EmClass
106 111
     def get_class_childs(self, uid):
@@ -111,24 +116,23 @@ class EditorialModel(object):
111 116
                 res.append(cls)
112 117
         return set(res)
113 118
 
114
-
115
-    ##@brief EmGroup getter
119
+    # @brief EmGroup getter
116 120
     # @param uid None | str : give this argument to get a specific EmGroup
117 121
     # @return if uid is given returns an EmGroup else returns an EmGroup iterator
118
-    def groups(self, uid = None):
122
+    def groups(self, uid=None):
119 123
         try:
120
-            return self.__elt_getter(   self.__active_groups,
121
-                                        uid)
124
+            return self.__elt_getter(self.__active_groups,
125
+                                     uid)
122 126
         except KeyError:
123 127
             raise EditorialModelException("EmGroup not found : '%s'" % uid)
124
-    
125
-    ##@brief Private getter for __groups or __classes
128
+
129
+    # @brief Private getter for __groups or __classes
126 130
     # @see classes() groups()
127 131
     def __elt_getter(self, elts, uid):
128 132
         return list(elts.values()) if uid is None else elts[uid]
129
-    
130
-    ##@brief Update the EditorialModel.__active_groups and
131
-    #EditorialModel.__active_classes attibutes
133
+
134
+    # @brief Update the EditorialModel.__active_groups and
135
+    # EditorialModel.__active_classes attibutes
132 136
     def __set_actives(self):
133 137
         if Settings.editorialmodel.editormode:
134 138
             logger.warning("All EM groups active because editormode in ON")
@@ -136,7 +140,7 @@ class EditorialModel(object):
136 140
             self.__active_groups = self.__groups
137 141
             self.__active_classes = self.__classes
138 142
         else:
139
-            #determine groups first
143
+            # determine groups first
140 144
             self.__active_groups = dict()
141 145
             self.__active_classes = dict()
142 146
             for agrp in Settings.editorialmodel.groups:
@@ -153,13 +157,13 @@ class EditorialModel(object):
153 157
                 raise RuntimeError("No active class found. Abording")
154 158
             for clsname, acls in self.__active_classes.items():
155 159
                 acls._set_active_fields(self.__active_groups)
156
-    
157
-    ##@brief EmField getter
160
+
161
+    # @brief EmField getter
158 162
     # @param uid str : An EmField uid represented by "CLASSUID.FIELDUID"
159 163
     # @return Fals or an EmField instance
160 164
     #
161 165
     # @todo delete it, useless...
162
-    def field(self, uid = None):
166
+    def field(self, uid=None):
163 167
         spl = uid.split('.')
164 168
         if len(spl) != 2:
165 169
             raise ValueError("Malformed EmField identifier : '%s'" % uid)
@@ -175,7 +179,7 @@ class EditorialModel(object):
175 179
             pass
176 180
         return False
177 181
 
178
-    ##@brief Add a class to the editorial model
182
+    # @brief Add a class to the editorial model
179 183
     # @param emclass EmClass : the EmClass instance to add
180 184
     # @return emclass
181 185
     def add_class(self, emclass):
@@ -187,7 +191,7 @@ class EditorialModel(object):
187 191
         self.__classes[emclass.uid] = emclass
188 192
         return emclass
189 193
 
190
-    ##@brief Add a group to the editorial model
194
+    # @brief Add a group to the editorial model
191 195
     # @param emgroup EmGroup : the EmGroup instance to add
192 196
     # @return emgroup
193 197
     def add_group(self, emgroup):
@@ -199,15 +203,15 @@ class EditorialModel(object):
199 203
         self.__groups[emgroup.uid] = emgroup
200 204
         return emgroup
201 205
 
202
-    ##@brief Add a new EmClass to the editorial model
206
+    # @brief Add a new EmClass to the editorial model
203 207
     #@param uid str : EmClass uid
204
-    #@param **kwargs : EmClass constructor options ( 
208
+    #@param **kwargs : EmClass constructor options (
205 209
     # see @ref lodel.editorial_model.component.EmClass.__init__() )
206 210
     def new_class(self, uid, **kwargs):
207 211
         assert_edit()
208 212
         return self.add_class(EmClass(uid, **kwargs))
209
-    
210
-    ##@brief Add a new EmGroup to the editorial model
213
+
214
+    # @brief Add a new EmGroup to the editorial model
211 215
     #@param uid str : EmGroup uid
212 216
     #@param *kwargs : EmGroup constructor keywords arguments (
213 217
     # see @ref lodel.editorial_model.component.EmGroup.__init__() )
@@ -215,7 +219,7 @@ class EditorialModel(object):
215 219
         assert_edit()
216 220
         return self.add_group(EmGroup(uid, **kwargs))
217 221
 
218
-    ##@brief Save a model
222
+    # @brief Save a model
219 223
     # @param translator module : The translator module to use
220 224
     # @param **translator_args
221 225
     def save(self, translator, **translator_kwargs):
@@ -223,14 +227,15 @@ class EditorialModel(object):
223 227
         if isinstance(translator, str):
224 228
             translator = self.translator_from_name(translator)
225 229
         return translator.save(self, **translator_kwargs)
226
-    
227
-    ##@brief Raise an error if lodel is not in EM edition mode
230
+
231
+    # @brief Raise an error if lodel is not in EM edition mode
228 232
     @staticmethod
229 233
     def raise_if_ro():
230 234
         if not Settings.editorialmodel.editormode:
231
-            raise EditorialModelError("Lodel in not in EM editor mode. The EM is in read only state")
235
+            raise EditorialModelError(
236
+                "Lodel in not in EM editor mode. The EM is in read only state")
232 237
 
233
-    ##@brief Load a model
238
+    # @brief Load a model
234 239
     # @param translator module : The translator module to use
235 240
     # @param **translator_args
236 241
     @classmethod
@@ -241,7 +246,7 @@ class EditorialModel(object):
241 246
         res.__set_actives()
242 247
         return res
243 248
 
244
-    ##@brief Return a translator module given a translator name
249
+    # @brief Return a translator module given a translator name
245 250
     # @param translator_name str : The translator name
246 251
     # @return the translator python module
247 252
     # @throw NameError if the translator does not exists
@@ -253,12 +258,12 @@ class EditorialModel(object):
253 258
         except ImportError:
254 259
             raise NameError("No translator named %s")
255 260
         return mod
256
-        
257
-    ##@brief Lodel hash
261
+
262
+    # @brief Lodel hash
258 263
     def d_hash(self):
259 264
         payload = "%s%s" % (
260
-                            self.name,
261
-                            'NODESC' if self.description is None else self.description.d_hash()
265
+            self.name,
266
+            'NODESC' if self.description is None else self.description.d_hash()
262 267
         )
263 268
         for guid in sorted(self.__groups):
264 269
             payload += str(self.__groups[guid].d_hash())
@@ -267,7 +272,6 @@ class EditorialModel(object):
267 272
             payload += str(self.__classes[cuid].d_hash())
268 273
 
269 274
         return int.from_bytes(
270
-                                hashlib.md5(bytes(payload, 'utf-8')).digest(),
271
-                                byteorder='big'
275
+            hashlib.md5(bytes(payload, 'utf-8')).digest(),
276
+            byteorder='big'
272 277
         )
273
-

+ 226
- 140
lodel/leapi/datahandlers/base_classes.py View File

@@ -12,34 +12,51 @@ import warnings
12 12
 from lodel.context import LodelContext
13 13
 
14 14
 LodelContext.expose_modules(globals(), {
15
-    'lodel.exceptions': ['LodelException', 'LodelExceptions',
16
-        'LodelFatalError', 'DataNoneValid', 'FieldValidationError'],
17
-    'lodel.leapi.datahandlers.exceptions': ['LodelDataHandlerConsistencyException', 'LodelDataHandlerException'],
18
-    'lodel.logger': 'logger'})
19
-
20
-
21
-##@brief Base class for all data handlers
22
-#@ingroup lodel2_datahandlers
23
-class DataHandler(object):
15
+    'lodel.exceptions': [
16
+        'LodelException',
17
+        'LodelExceptions',
18
+        'LodelFatalError',
19
+        'DataNoneValid',
20
+        'FieldValidationError'
21
+    ],
22
+    'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
23
+    'lodel.leapi.datahandlers.exceptions': [
24
+        'LodelDataHandlerConsistencyException',
25
+        'LodelDataHandlerException'
26
+    ],
27
+    'lodel.validator.validator': [
28
+        'ValidationError'
29
+    ],
30
+    'lodel.logger': 'logger',
31
+    'lodel.utils.mlstring': ['MlString']})
32
+
33
+
34
+## @brief Base class for all data handlers
35
+# @ingroup lodel2_datahandlers
36
+class DataHandler(MlNamedObject):
24 37
     base_type = "type"
25 38
     _HANDLERS_MODULES = ('datas_base', 'datas', 'references')
26
-    ##@brief Stores the DataHandler childs classes indexed by name
39
+    ## @brief Stores the DataHandler childs classes indexed by name
27 40
     _base_handlers = None
28
-    ##@brief Stores custom datahandlers classes indexed by name
41
+    ## @brief Stores custom datahandlers classes indexed by name
29 42
     # @todo do it ! (like plugins, register handlers... blablabla)
30 43
     __custom_handlers = dict()
31 44
 
32 45
     help_text = 'Generic Field Data Handler'
46
+    display_name = "Generic Field"
47
+    options_spec = dict()
48
+    options_values = dict()
33 49
 
34
-    ##@brief List fields that will be exposed to the construct_data_method
50
+    ## @brief List fields that will be exposed to the construct_data_method
35 51
     _construct_datas_deps = []
36 52
 
37 53
     directly_editable = True
38
-    ##@brief constructor
54
+
55
+    ## @brief constructor
56
+    #
39 57
     # @param internal False | str : define whether or not a field is internal
40
-    # @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
41
-    #                         designed globally and immutable
42
-    # @param **args
58
+    # @param immutable bool : indicates if the fieldtype has to be defined in child classes of
59
+    #                         LeObject or if it is designed globally and immutable
43 60
     # @throw NotImplementedError if it is instanciated directly
44 61
     def __init__(self, **kwargs):
45 62
         if self.__class__ == DataHandler:
@@ -54,9 +71,30 @@ class DataHandler(object):
54 71
             self.default, error = self.check_data_value(kwargs['default'])
55 72
             if error:
56 73
                 raise error
57
-            del(kwargs['default'])
74
+            del kwargs['default']
58 75
         for argname, argval in kwargs.items():
59 76
             setattr(self, argname, argval)
77
+        self.check_options()
78
+
79
+        display_name = kwargs.get('display_name',MlString(self.display_name))
80
+        help_text = kwargs.get('help_text', MlString(self.help_text))
81
+        super().__init__(display_name, help_text)
82
+
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
86
+    def check_options(self):
87
+        for option_name, option_datas in self.options_spec.items():
88
+            if option_name in self.options_values:
89
+                # There is a configured option, we check its value
90
+                try:
91
+                    self.options_values[option_name] = option_datas[1].check_value(
92
+                        self.options_values[option_name])
93
+                except ValueError:
94
+                    pass  # TODO Deal with the case where the value used for an option is invalid
95
+            else:
96
+                # This option was not configured, we get the default value from the specs
97
+                self.options_values[option_name] = option_datas[0]
60 98
 
61 99
     ## Fieldtype name
62 100
     @classmethod
@@ -74,15 +112,15 @@ class DataHandler(object):
74 112
     def is_primary_key(self):
75 113
         return self.primary_key
76 114
 
77
-    ##@brief checks if a fieldtype is internal
115
+    ## @brief checks if a fieldtype is internal
78 116
     # @return bool
79 117
     def is_internal(self):
80 118
         return self.internal is not False
81 119
 
82
-    ##brief check if a value can be nullable
83
-    #@param value *
84
-    #@throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
85
-    #@return value (if not None)
120
+    ## @brief check if a value can be nullable
121
+    # @param value *
122
+    # @throw DataNoneValid if value is None and nullable. LodelExceptions if not nullable
123
+    # @return value (if not None)
86 124
     # @return value
87 125
     def _check_data_value(self, value):
88 126
         if value is None:
@@ -91,9 +129,9 @@ class DataHandler(object):
91 129
             raise DataNoneValid("None with a nullable. This exeption is allowed")
92 130
         return value
93 131
 
94
-    ##@brief calls the data_field (defined in derived class) _check_data_value() method
95
-    #@param value *
96
-    #@return tuple (value|None, None|error) value can be cast if NoneError
132
+    ## @brief calls the data_field (defined in derived class) _check_data_value() method
133
+    # @param value *
134
+    # @return tuple (value|None, None|error) value can be cast if NoneError
97 135
     def check_data_value(self, value):
98 136
         try:
99 137
             value = self._check_data_value(value)
@@ -103,7 +141,7 @@ class DataHandler(object):
103 141
             return None, expt
104 142
         return value, None
105 143
 
106
-    ##@brief checks if this class can override the given data handler
144
+    ## @brief checks if this class can override the given data handler
107 145
     # @param data_handler DataHandler
108 146
     # @return bool
109 147
     def can_override(self, data_handler):
@@ -111,17 +149,17 @@ class DataHandler(object):
111 149
             return False
112 150
         return True
113 151
 
114
-    ##@brief Build field value
115
-    #@ingroup lodel2_dh_checks
116
-    #@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
117
-    #@ref _construct_data() and @ref lodel2_dh_check_impl )
118
-    #@param emcomponent EmComponent : An EmComponent child class instance
119
-    #@param fname str : The field name
120
-    #@param datas dict : dict storing fields values (from the component)
121
-    #@param cur_value : the value from the current field (identified by fieldname)
122
-    #@return the value
123
-    #@throw RunTimeError if data construction fails
124
-    #@todo raise something else
152
+    ## @brief Build field value
153
+    # @ingroup lodel2_dh_checks
154
+    # @warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
155
+    # @ref _construct_data() and @ref lodel2_dh_check_impl )
156
+    # @param emcomponent EmComponent : An EmComponent child class instance
157
+    # @param fname str : The field name
158
+    # @param datas dict : dict storing fields values (from the component)
159
+    # @param cur_value : the value from the current field (identified by fieldname)
160
+    # @return the value
161
+    # @throw RunTimeError if data construction fails
162
+    # @todo raise something else
125 163
     def construct_data(self, emcomponent, fname, datas, cur_value):
126 164
         emcomponent_fields = emcomponent.fields()
127 165
         data_handler = None
@@ -136,41 +174,41 @@ class DataHandler(object):
136 174
             new_val = None
137 175
         return self._construct_data(emcomponent, fname, datas, new_val)
138 176
 
139
-    ##@brief Designed to be reimplemented by child classes
140
-    #@param emcomponent EmComponent : An EmComponent child class instance
141
-    #@param fname str : The field name
142
-    #@param datas dict : dict storing fields values (from the component)
143
-    #@param cur_value : the value from the current field (identified by fieldname)
144
-    #@return the value
145
-    #@see construct_data() lodel2_dh_check_impl
177
+    ## @brief Designed to be reimplemented by child classes
178
+    # @param emcomponent EmComponent : An EmComponent child class instance
179
+    # @param fname str : The field name
180
+    # @param datas dict : dict storing fields values (from the component)
181
+    # @param cur_value : the value from the current field (identified by fieldname)
182
+    # @return the value
183
+    # @see construct_data() lodel2_dh_check_impl
146 184
     def _construct_data(self, empcomponent, fname, datas, cur_value):
147 185
         return cur_value
148 186
 
149
-    ##@brief Check datas consistency
150
-    #@ingroup lodel2_dh_checks
151
-    #@warning DO NOT REIMPLEMENT THIS METHOD IN A CUSTOM DATAHANDLER (see
152
-    #@ref _construct_data() and @ref lodel2_dh_check_impl )
153
-    #@warning the datas argument looks like a dict but is not a dict
154
-    #see @ref base_classes.DatasConstructor "DatasConstructor" and
155
-    #@ref lodel2_dh_datas_construction "Datas construction section"
156
-    #@param emcomponent EmComponent : An EmComponent child class instance
157
-    #@param fname : the field name
158
-    #@param datas dict : dict storing fields values
159
-    #@return an Exception instance if fails else True
160
-    #@todo A implémenter
187
+    ## @brief Check datas consistency
188
+    # @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"
194
+    # @param emcomponent EmComponent : An EmComponent child class instance
195
+    # @param fname : the field name
196
+    # @param datas dict : dict storing fields values
197
+    # @return an Exception instance if fails else True
198
+    # @todo A implémenter
161 199
     def check_data_consistency(self, emcomponent, fname, datas):
162 200
         return self._check_data_consistency(emcomponent, fname, datas)
163 201
 
164
-    ##@brief Designed to be reimplemented by child classes
165
-    #@param emcomponent EmComponent : An EmComponent child class instance
166
-    #@param fname : the field name
167
-    #@param datas dict : dict storing fields values
168
-    #@return an Exception instance if fails else True
169
-    #@see check_data_consistency() lodel2_dh_check_impl
202
+    ## @brief Designed to be reimplemented by child classes
203
+    # @param emcomponent EmComponent : An EmComponent child class instance
204
+    # @param fname : the field name
205
+    # @param datas dict : dict storing fields values
206
+    # @return an Exception instance if fails else True
207
+    # @see check_data_consistency() lodel2_dh_check_impl
170 208
     def _check_data_consistency(self, emcomponent, fname, datas):
171 209
         return True
172 210
 
173
-    ##@brief make consistency after a query
211
+    ## @brief make consistency after a query
174 212
     # @param emcomponent EmComponent : An EmComponent child class instance
175 213
     # @param fname : the field name
176 214
     # @param datas dict : dict storing fields values
@@ -179,7 +217,7 @@ class DataHandler(object):
179 217
     def make_consistency(self, emcomponent, fname, datas):
180 218
         pass
181 219
 
182
-    ##@brief This method is use by plugins to register new data handlers
220
+    ## @brief This method is use by plugins to register new data handlers
183 221
     @classmethod
184 222
     def register_new_handler(cls, name, data_handler):
185 223
         if not inspect.isclass(data_handler):
@@ -188,7 +226,7 @@ class DataHandler(object):
188 226
             raise ValueError("A data handler HAS TO be a child class of DataHandler")
189 227
         cls.__custom_handlers[name] = data_handler
190 228
 
191
-    ##@brief Load all datahandlers
229
+    ## @brief Load all datahandlers
192 230
     @classmethod
193 231
     def load_base_handlers(cls):
194 232
         if cls._base_handlers is None:
@@ -201,10 +239,11 @@ class DataHandler(object):
201 239
                         cls._base_handlers[name.lower()] = obj
202 240
         return copy.copy(cls._base_handlers)
203 241
 
204
-    ##@brief given a field type name, returns the associated python class
242
+    ## @brief given a field type name, returns the associated python class
205 243
     # @param fieldtype_name str : A field type name (not case sensitive)
206 244
     # @return DataField child class
207
-    # @note To access custom data handlers it can be cool to prefix the handler name by plugin name for example ? (to ensure name unicity)
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)
208 247
     @classmethod
209 248
     def from_name(cls, name):
210 249
         cls.load_base_handlers()
@@ -214,7 +253,24 @@ class DataHandler(object):
214 253
             raise NameError("No data handlers named '%s'" % (name,))
215 254
         return all_handlers[name]
216 255
 
217
-    ##@brief Return the module name to import in order to use the datahandler
256
+    # @brief List all datahandlers
257
+    # @return a dict with, display_name for keys, and a dict for value 
258
+    @classmethod
259
+    def list_data_handlers(cls):
260
+        cls.load_base_handlers()
261
+        all_handlers = dict(cls._base_handlers, **cls.__custom_handlers)
262
+        list_dh = dict()
263
+        for hdl in all_handlers:
264
+            list_dh[hdl.display_name] = {'help_text' : hdl.help_text,
265
+                            'nullable' : hdl.nullable, \
266
+                            'internal' : hdl.internal,
267
+                            'immutable' : hdl.immutable, \
268
+                            'primary_key' : hdl.primary_key, \
269
+                            'options' : self.options_spec}
270
+
271
+        return list_dh
272
+
273
+    ## @brief Return the module name to import in order to use the datahandler
218 274
     # @param data_handler_name str : Data handler name
219 275
     # @return a str
220 276
     @classmethod
@@ -222,62 +278,64 @@ class DataHandler(object):
222 278
         name = name.lower()
223 279
         handler_class = cls.from_name(name)
224 280
         return '{module_name}.{class_name}'.format(
225
-                                                    module_name=handler_class.__module__,
226
-                                                    class_name=handler_class.__name__
281
+            module_name=handler_class.__module__,
282
+            class_name=handler_class.__name__
227 283
         )
228 284
 
229
-    ##@brief __hash__ implementation for fieldtypes
285
+    ## @brief __hash__ implementation for fieldtypes
230 286
     def __hash__(self):
231 287
         hash_dats = [self.__class__.__module__]
232 288
         for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
233 289
             hash_dats.append((kdic, getattr(self, kdic)))
234 290
         return hash(tuple(hash_dats))
235 291
 
236
-##@brief Base class for datas data handler (by opposition with references)
237
-#@ingroup lodel2_datahandlers
292
+## @brief Base class for datas data handler (by opposition with references)
293
+# @ingroup lodel2_datahandlers
238 294
 class DataField(DataHandler):
239 295
     pass
240 296
 
241
-##@brief Abstract class for all references
242
-#@ingroup lodel2_datahandlers
297
+
298
+## @brief Abstract class for all references
299
+# @ingroup lodel2_datahandlers
243 300
 #
244 301
 # References are fields that stores a reference to another
245 302
 # editorial object
246
-#@todo Construct data implementation : transform the data into a LeObject
247
-#instance
248
-
303
+# @todo Construct data implementation : transform the data into a LeObject instance
249 304
 class Reference(DataHandler):
250 305
     base_type = "ref"
251 306
 
252
-    ##@brief Instanciation
307
+    ## @brief Instanciation
253 308
     # @param allowed_classes list | None : list of allowed em classes if None no restriction
254 309
     # @param back_reference tuple | None : tuple containing (LeObject child class, fieldname)
255 310
     # @param internal bool : if False, the field is not internal
256 311
     # @param **kwargs : other arguments
257 312
     def __init__(self, allowed_classes=None, back_reference=None, internal=False, **kwargs):
258 313
         self.__allowed_classes = set() if allowed_classes is None else set(allowed_classes)
259
-        self.allowed_classes = list() if allowed_classes is None else allowed_classes # For now usefull to jinja 2
314
+        # For now usefull to jinja 2
315
+        self.allowed_classes = list() if allowed_classes is None else allowed_classes
260 316
         if back_reference is not None:
261 317
             if len(back_reference) != 2:
262 318
                 raise ValueError("A tuple (classname, fieldname) expected but got '%s'" % back_reference)
263
-            #if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0]) or not isinstance(back_reference[1], str):
264
-            #    raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str) but got : (%s, %s)" % (back_reference[0], back_reference[1]))
319
+            # if not issubclass(lodel.leapi.leobject.LeObject, back_reference[0])
320
+            # or not isinstance(back_reference[1], str):
321
+            # raise TypeError("Back reference was expected to be a tuple(<class LeObject>, str)
322
+            # but got : (%s, %s)" % (back_reference[0], back_reference[1]))
265 323
         self.__back_reference = back_reference
266 324
         super().__init__(internal=internal, **kwargs)
267 325
 
268
-    ##@brief Method designed to return an empty value for this kind of
269
-    #multipleref
326
+    ## @brief Method designed to return an empty value for this kind of
327
+    # multipleref
270 328
     @classmethod
271 329
     def empty(cls):
272 330
         return None
273 331
 
274
-    ##@brief Property that takes value of a copy of the back_reference tuple
332
+    ## @brief Property that takes value of a copy of the back_reference tuple
275 333
     @property
276 334
     def back_reference(self):
277 335
         return copy.copy(self.__back_reference)
278 336
 
279
-    ##@brief Property that takes value of datahandler of the backreference or
280
-    #None
337
+    ## @brief Property that takes value of datahandler of the backreference or
338
+    # None
281 339
     @property
282 340
     def back_ref_datahandler(self):
283 341
         if self.__back_reference is None:
@@ -288,15 +346,15 @@ class Reference(DataHandler):
288 346
     def linked_classes(self):
289 347
         return copy.copy(self.__allowed_classes)
290 348
 
291
-    ##@brief Set the back reference for this field.
349
+    ## @brief Set the back reference for this field.
292 350
     def _set_back_reference(self, back_reference):
293 351
         self.__back_reference = back_reference
294 352
 
295
-    ##@brief Check and cast value in appropriate type
296
-    #@param value *
297
-    #@throw FieldValidationError if value is an appropriate type
298
-    #@return value
299
-    #@todo implement the check when we have LeObject uid check value
353
+    ## @brief Check and cast value in appropriate type
354
+    # @param value *
355
+    # @throw FieldValidationError if value is an appropriate type
356
+    # @return value
357
+    # @todo implement the check when we have LeObject uid check value
300 358
     def _check_data_value(self, value):
301 359
         from lodel.leapi.leobject import LeObject
302 360
         value = super()._check_data_value(value)
@@ -311,13 +369,13 @@ class Reference(DataHandler):
311 369
                 raise FieldValidationError("Reference datahandler can not check this value %s if any allowed_class is allowed." % value)
312 370
         return value
313 371
 
314
-    ##@brief Check datas consistency
315
-    #@param emcomponent EmComponent : An EmComponent child class instance
316
-    #@param fname : the field name
317
-    #@param datas dict : dict storing fields values
318
-    #@return an Exception instance if fails else True
319
-    #@todo check for performance issue and check logics
320
-    #@warning composed uid capabilities broken here
372
+    ## @brief Check datas consistency
373
+    # @param emcomponent EmComponent : An EmComponent child class instance
374
+    # @param fname : the field name
375
+    # @param datas dict : dict storing fields values
376
+    # @return an Exception instance if fails else True
377
+    # @todo check for performance issue and check logics
378
+    # @warning composed uid capabilities broken here
321 379
     def check_data_consistency(self, emcomponent, fname, datas):
322 380
         rep = super().check_data_consistency(emcomponent, fname, datas)
323 381
         if isinstance(rep, Exception):
@@ -333,21 +391,21 @@ class Reference(DataHandler):
333 391
         if not target_class.is_exist(value):
334 392
             logger.warning('Object referenced does not exist')
335 393
             return False
336
-        #target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
337
-        #obj = target_class.get([(target_uidfield, '=', value)])
338
-        #if len(obj) == 0:
394
+        # target_uidfield = target_class.uid_fieldname()[0] #multi uid broken here
395
+        # obj = target_class.get([(target_uidfield, '=', value)])
396
+        # if len(obj) == 0:
339 397
         #    logger.warning('Object referenced does not exist')
340 398
         #    return False
341 399
         return True
342
-    
343
-    ##@brief Utility method designed to fetch referenced objects
344
-    #@param value mixed : the field value
345
-    #@throw NotImplementedError
400
+
401
+    ## @brief Utility method designed to fetch referenced objects
402
+    # @param value mixed : the field value
403
+    # @throw NotImplementedError
346 404
     def get_referenced(self, value):
347 405
         raise NotImplementedError
348 406
 
349 407
 
350
-##@brief This class represent a data_handler for single reference to another object
408
+## @brief This class represent a data_handler for single reference to another object
351 409
 #
352 410
 # The fields using this data handlers are like "foreign key" on another object
353 411
 class SingleRef(Reference):
@@ -356,18 +414,18 @@ class SingleRef(Reference):
356 414
         super().__init__(allowed_classes=allowed_classes, **kwargs)
357 415
 
358 416
 
359
-    ##@brief Check and cast value in appropriate type
360
-    #@param value: *
361
-    #@throw FieldValidationError if value is unappropriate or can not be cast
362
-    #@return value
417
+    ## @brief Check and cast value in appropriate type
418
+    # @param value: *
419
+    # @throw FieldValidationError if value is unappropriate or can not be cast
420
+    # @return value
363 421
     def _check_data_value(self, value):
364 422
         value = super()._check_data_value(value)
365 423
         return value
366 424
 
367
-    ##@brief Utility method designed to fetch referenced objects
368
-    #@param value mixed : the field value
369
-    #@return A LeObject child class instance
370
-    #@throw LodelDataHandlerConsistencyException if no referenced object found
425
+    ## @brief Utility method designed to fetch referenced objects
426
+    # @param value mixed : the field value
427
+    # @return A LeObject child class instance
428
+    # @throw LodelDataHandlerConsistencyException if no referenced object found
371 429
     def get_referenced(self, value):
372 430
         for leo_cls in self.linked_classes:
373 431
             res = leo_cls.get_from_uid(value)
@@ -377,30 +435,30 @@ class SingleRef(Reference):
377 435
 referenced object with uid %s" % value)
378 436
 
379 437
 
380
-##@brief This class represent a data_handler for multiple references to another object
381
-#@ingroup lodel2_datahandlers
438
+## @brief This class represent a data_handler for multiple references to another object
439
+# @ingroup lodel2_datahandlers
382 440
 #
383 441
 # The fields using this data handlers are like SingleRef but can store multiple references in one field
384 442
 # @note for the moment split on ',' chars
385 443
 class MultipleRef(Reference):
386 444
 
387
-    ##
445
+    ## @brief Constructor
388 446
     # @param max_item int | None : indicate the maximum number of item referenced by this field, None mean no limit
389 447
     def __init__(self, max_item=None, **kwargs):
390 448
         self.max_item = max_item
391 449
         super().__init__(**kwargs)
392 450
 
393
-    ##@brief Method designed to return an empty value for this kind of
394
-    #multipleref
451
+    ## @brief Method designed to return an empty value for this kind of
452
+    # multipleref
395 453
     @classmethod
396 454
     def empty(cls):
397 455
         return []
398 456
 
399
-    ##@brief Check and cast value in appropriate type
400
-    #@param value *
401
-    #@throw FieldValidationError if value is unappropriate or can not be cast
402
-    #@return value
403
-    #@TODO  Writing test error for errors when stored multiple references in one field
457
+    ## @brief Check and cast value in appropriate type
458
+    # @param value *
459
+    # @throw FieldValidationError if value is unappropriate or can not be cast
460
+    # @return value
461
+    # @TODO  Writing test error for errors when stored multiple references in one field
404 462
     def _check_data_value(self, value):
405 463
         value = DataHandler._check_data_value(self, value)
406 464
         if not hasattr(value, '__iter__'):
@@ -408,7 +466,7 @@ class MultipleRef(Reference):
408 466
         if self.max_item is not None:
409 467
             if self.max_item < len(value):
410 468
                 raise FieldValidationError("Too many items")
411
-        new_val = list() 
469
+        new_val = list()
412 470
         error_list = list()
413 471
         for i, v in enumerate(value):
414 472
             try:
@@ -420,11 +478,11 @@ class MultipleRef(Reference):
420 478
             raise FieldValidationError("MultipleRef have for invalid values [%s]  :" % (",".join(error_list)))
421 479
         return new_val
422 480
 
423
-    ##@brief Utility method designed to fetch referenced objects
424
-    #@param values mixed : the field values
425
-    #@return A list of LeObject child class instance
426
-    #@throw LodelDataHandlerConsistencyException if some referenced objects
427
-    #were not found
481
+    ## @brief Utility method designed to fetch referenced objects
482
+    # @param values mixed : the field values
483
+    # @return A list of LeObject child class instance
484
+    # @throw LodelDataHandlerConsistencyException if some referenced objects
485
+    # were not found
428 486
     def get_referenced(self, values):
429 487
         if values is None or len(values) == 0:
430 488
             return list()
@@ -432,18 +490,19 @@ class MultipleRef(Reference):
432 490
         values = set(values)
433 491
         res = list()
434 492
         for leo_cls in self.linked_classes:
435
-            uidname = leo_cls.uid_fieldname()[0] #MULTIPLE UID BROKEN HERE
493
+            uidname = leo_cls.uid_fieldname()[0]  # MULTIPLE UID BROKEN HERE
436 494
             tmp_res = leo_cls.get(('%s in (%s)' % (uidname, ','.join(
437 495
                 [str(l) for l in left]))))
438
-            left ^= set(( leo.uid() for leo in tmp_res))
496
+            left ^= set((leo.uid() for leo in tmp_res))
439 497
             res += tmp_res
440 498
             if len(left) == 0:
441 499
                 return res
442 500
         raise LodelDataHandlerConsistencyException("Unable to find \
443 501
 some referenced objects. Following uids were not found : %s" % ','.join(left))
444 502
 
503
+
445 504
 ## @brief Class designed to handle datas access will fieldtypes are constructing datas
446
-#@ingroup lodel2_datahandlers
505
+# @ingroup lodel2_datahandlers
447 506
 #
448 507
 # This class is designed to allow automatic scheduling of construct_data calls.
449 508
 #
@@ -457,15 +516,15 @@ class DatasConstructor(object):
457 516
     # @param datas dict : dict with field name as key and field values as value
458 517
     # @param fields_handler dict : dict with field name as key and data handler instance as value
459 518
     def __init__(self, leobject, datas, fields_handler):
460
-        ## Stores concerned class
519
+        # Stores concerned class
461 520
         self._leobject = leobject
462
-        ## Stores datas and constructed datas
521
+        # Stores datas and constructed datas
463 522
         self._datas = copy.copy(datas)
464
-        ## Stores fieldtypes
523
+        # Stores fieldtypes
465 524
         self._fields_handler = fields_handler
466
-        ## Stores list of fieldname for constructed datas
525
+        # Stores list of fieldname for constructed datas
467 526
         self._constructed = []
468
-        ## Stores construct calls list
527
+        # Stores construct calls list
469 528
         self._construct_calls = []
470 529
 
471 530
     ## @brief Implements the dict.keys() method on instance
@@ -488,3 +547,30 @@ class DatasConstructor(object):
488 547
         self._datas[fname] = value
489 548
         warnings.warn("Setting value of an DatasConstructor instance")
490 549
 
550
+
551
+## @brief Class designed to handle an option of a DataHandler
552
+class DatahandlerOption(MlNamedObject):
553
+
554
+    ## @brief instanciates a new Datahandler option object
555
+    #
556
+    # @param id str
557
+    # @param display_name MlString
558
+    # @param help_text MlString
559
+    # @param validator function
560
+    def __init__(self, id, display_name, help_text, validator):
561
+        self.__id = id
562
+        self.__validator = validator
563
+        super().__init__(display_name, help_text)
564
+
565
+    @property
566
+    def id(self):
567
+        return self.__id
568
+
569
+    ## @brief checks a value corresponding to this option is valid
570
+    # @param value
571
+    # @return casted value
572
+    def check_value(self, value):
573
+        try:
574
+            return self.__validator(value)
575
+        except ValidationError:
576
+            raise ValueError()

+ 10
- 11
lodel/leapi/datahandlers/datas.py View File

@@ -7,9 +7,9 @@ from lodel.context import LodelContext
7 7
 
8 8
 LodelContext.expose_modules(globals(), {
9 9
     'lodel.leapi.datahandlers.datas_base': ['Boolean', 'Integer', 'Varchar',
10
-        'DateTime', 'Text', 'File'],
10
+                                            'DateTime', 'Text', 'File'],
11 11
     'lodel.exceptions': ['LodelException', 'LodelExceptions',
12
-        'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
12
+                         'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
13 13
 
14 14
 
15 15
 ##@brief Data field designed to handle formated strings
@@ -26,7 +26,7 @@ build its content'
26 26
     def __init__(self, format_string, field_list, **kwargs):
27 27
         self._field_list = field_list
28 28
         self._format_string = format_string
29
-        super().__init__(internal='automatic',**kwargs)
29
+        super().__init__(internal='automatic', **kwargs)
30 30
 
31 31
     def _construct_data(self, emcomponent, fname, datas, cur_value):
32 32
         ret = self._format_string % tuple(
@@ -49,7 +49,7 @@ max_length and regex'
49 49
     # @param **kwargs
50 50
     def __init__(self, regex='', max_length=10, **kwargs):
51 51
         self.regex = regex
52
-        self.compiled_re = re.compile(regex)#trigger an error if invalid regex
52
+        self.compiled_re = re.compile(regex)  # trigger an error if invalid regex
53 53
         super(self.__class__, self).__init__(max_length=max_length, **kwargs)
54 54
 
55 55
     ##@brief Check and cast value in appropriate type
@@ -106,7 +106,8 @@ be internal")
106 106
         if not inspect.isclass(emcomponent):
107 107
             cls = emcomponent.__class__
108 108
         return cls.__name__
109
-        
109
+
110
+
110 111
 ##@brief Data field designed to handle concatenated fields
111 112
 class Concat(FormatString):
112 113
     help = 'Automatic strings concatenation'
@@ -116,11 +117,11 @@ class Concat(FormatString):
116 117
     # @param field_list list : List of field to use
117 118
     # @param separator str
118 119
     # @param **kwargs    
119
-    def __init__(self, field_list, separator = ' ', **kwargs):
120
+    def __init__(self, field_list, separator=' ', **kwargs):
120 121
         format_string = separator.join(['%s' for _ in field_list])
121
-        super().__init__(
122
-            format_string = format_string, field_list = field_list, **kwargs)
123
-
122
+        super().__init__(format_string=format_string,
123
+                         field_list=field_list,
124
+                         **kwargs)
124 125
 
125 126
 
126 127
 class Password(Varchar):
@@ -129,7 +130,6 @@ class Password(Varchar):
129 130
     pass
130 131
 
131 132
 
132
-
133 133
 class VarcharList(Varchar):
134 134
     help = 'DataHandler designed to make a list out of a string.'
135 135
     base_type = 'varchar'
@@ -140,7 +140,6 @@ class VarcharList(Varchar):
140 140
         self.delimiter = str(delimiter)
141 141
         super().__init__(**kwargs)
142 142
 
143
-
144 143
     def construct_data(self, emcomponent, fname, datas, cur_value):
145 144
         result = cur_value.split(self.delimiter)
146 145
         return result

+ 38
- 34
lodel/leapi/datahandlers/datas_base.py View File

@@ -8,32 +8,33 @@ from lodel.context import LodelContext
8 8
 LodelContext.expose_modules(globals(), {
9 9
     'lodel.leapi.datahandlers.base_classes': ['DataField'],
10 10
     'lodel.exceptions': ['LodelException', 'LodelExceptions',
11
-        'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
11
+                         'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
12 12
 
13 13
 
14
-##@brief Data field designed to handle boolean values
14
+## @brief Data field designed to handle boolean values
15 15
 class Boolean(DataField):
16 16
 
17 17
     help = 'A basic boolean field'
18 18
     base_type = 'bool'
19 19
 
20
-    ##@brief A boolean field
20
+    ## @brief A boolean field
21 21
     def __init__(self, **kwargs):
22 22
         #if 'check_data_value' not in kwargs:
23 23
         #    kwargs['check_data_value'] = self._check_data_value
24 24
         super().__init__(ftype='bool', **kwargs)
25 25
 
26
-    ##@brief Check and cast value in appropriate type
27
-    #@param value *
28
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
29
-    #@return value
26
+    ## @brief Check and cast value in appropriate type
27
+    # @param value *
28
+    # @throw FieldValidationError if value is unappropriate or can not be cast
29
+    # @return value
30 30
     def _check_data_value(self, value):
31 31
         value = super()._check_data_value(value)   
32 32
         if not isinstance(value, bool):
33 33
             raise FieldValidationError("The value '%s' is not, and will never, be a boolean" % value)
34 34
         return value
35 35
 
36
-##@brief Data field designed to handle integer values
36
+
37
+## @brief Data field designed to handle integer values
37 38
 class Integer(DataField):
38 39
 
39 40
     help = 'Basic integer field'
@@ -41,14 +42,14 @@ class Integer(DataField):
41 42
     cast_type = int
42 43
 
43 44
     def __init__(self, **kwargs):
44
-        super().__init__( **kwargs)
45
+        super().__init__(**kwargs)
45 46
 
46
-    ##@brief Check and cast value in appropriate type
47
+    ## @brief Check and cast value in appropriate type
47 48
     # @param value *
48 49
     # @param strict bool : tells if the value must be an integer or a value that can be converted into an integer
49 50
     # @throw FieldValidationError if value is unappropriate or can not be cast
50 51
     # @return value
51
-    def _check_data_value(self, value, strict = False):
52
+    def _check_data_value(self, value, strict=False):
52 53
         value = super()._check_data_value(value)
53 54
         if (strict and not isinstance(value, int)):
54 55
             raise FieldValidationError("The value '%s' is not a python type integer" % value)
@@ -61,19 +62,20 @@ class Integer(DataField):
61 62
             raise FieldValidationError("The value '%s' is not, and will never, be an integer" % value)
62 63
         return value
63 64
 
64
-##@brief Data field designed to handle string
65
+
66
+## @brief Data field designed to handle string
65 67
 class Varchar(DataField):
66 68
 
67 69
     help = 'Basic string (varchar) field. Default size is 64 characters'
68 70
     base_type = 'char'
69 71
 
70
-    ##@brief A string field
72
+    ## @brief A string field
71 73
     # @brief max_length int: The maximum length of this field
72 74
     def __init__(self, max_length=64, **kwargs):
73 75
         self.max_length = int(max_length)
74 76
         super().__init__(**kwargs)
75 77
 
76
-    ##@brief checks if this class can override the given data handler
78
+    ## @brief checks if this class can override the given data handler
77 79
     # @param data_handler DataHandler
78 80
     # @return bool
79 81
     def can_override(self, data_handler):
@@ -83,25 +85,26 @@ class Varchar(DataField):
83 85
             return False
84 86
         return True
85 87
     
86
-    ##@brief Check and cast value in appropriate type
87
-    #@param value *
88
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
89
-    #@return value
88
+    ## @brief Check and cast value in appropriate type
89
+    # @param value *
90
+    # @throw FieldValidationError if value is unappropriate or can not be cast
91
+    # @return value
90 92
     def _check_data_value(self, value):
91 93
         value = super()._check_data_value(value)   
92 94
         if not isinstance(value, str):
93 95
             raise FieldValidationError("The value '%s' can't be a str" % value)
94 96
         if len(value) > self.max_length:
95
-             raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
97
+            raise FieldValidationError("The value '%s' is longer than the maximum length of this field (%s)" % (value, self.max_length))
96 98
         return value
97 99
 
98
-##@brief Data field designed to handle date & time 
100
+
101
+## @brief Data field designed to handle date & time
99 102
 class DateTime(DataField):
100 103
 
101 104
     help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
102 105
     base_type = 'datetime'
103 106
 
104
-    ##@brief A datetime field
107
+    ## @brief A datetime field
105 108
     # @param now_on_update bool : If true, the date is set to NOW on update
106 109
     # @param now_on_create bool : If true, the date is set to NEW on creation
107 110
     # @param **kwargs
@@ -111,13 +114,13 @@ class DateTime(DataField):
111 114
         self.datetime_format = '%Y-%m-%d' if 'format' not in kwargs else kwargs['format']
112 115
         super().__init__(**kwargs)
113 116
 
114
-    ##@brief Check and cast value in appropriate type
115
-    #@param value *
116
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
117
-    #@return value
117
+    ## @brief Check and cast value in appropriate type
118
+    # @param value *
119
+    # @throw FieldValidationError if value is unappropriate or can not be cast
120
+    # @return value
118 121
     def _check_data_value(self, value):
119 122
         value = super()._check_data_value(value)
120
-        if isinstance(value,str):
123
+        if isinstance(value, str):
121 124
             try:
122 125
                 value = datetime.datetime.fromtimestamp(time.mktime(time.strptime(value, self.datetime_format)))
123 126
             except ValueError:
@@ -131,7 +134,8 @@ class DateTime(DataField):
131 134
             return datetime.datetime.now()
132 135
         return cur_value
133 136
 
134
-##@brief Data field designed to handle long string
137
+
138
+## @brief Data field designed to handle long string
135 139
 class Text(DataField):
136 140
     help = 'A text field (big string)'
137 141
     base_type = 'text'
@@ -139,22 +143,23 @@ class Text(DataField):
139 143
     def __init__(self, **kwargs):
140 144
         super(self.__class__, self).__init__(ftype='text', **kwargs)
141 145
     
142
-    ##@brief Check and cast value in appropriate type
143
-    #@param value *
144
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
145
-    #@return value
146
+    ## @brief Check and cast value in appropriate type
147
+    # @param value *
148
+    # @throw FieldValidationError if value is unappropriate or can not be cast
149
+    # @return value
146 150
     def _check_data_value(self, value):
147 151
         value = super()._check_data_value(value)   
148 152
         if not isinstance(value, str):
149 153
             raise FieldValidationError("The content passed to this Text field is not a convertible to a string")
150 154
         return value
151 155
 
152
-##@brief Data field designed to handle Files
156
+
157
+## @brief Data field designed to handle Files
153 158
 class File(DataField):
154 159
 
155 160
     base_type = 'file'
156 161
 
157
-    ##@brief a file field
162
+    ## @brief a file field
158 163
     # @param upload_path str : None by default
159 164
     # @param **kwargs
160 165
     def __init__(self, upload_path=None, **kwargs):
@@ -164,4 +169,3 @@ class File(DataField):
164 169
     # @todo Add here a check for the validity of the given value (should have a correct path syntax)
165 170
     def _check_data_value(self, value):
166 171
         return super()._check_data_value(value)   
167
-

+ 3
- 2
lodel/leapi/datahandlers/exceptions.py View File

@@ -1,5 +1,6 @@
1
-def LodelDataHandlerException(Exception):
1
+class LodelDataHandlerException(Exception):
2 2
     pass
3 3
 
4
-def LodelDataHandlerConsistencyException(LodelDataHandlerException):
4
+
5
+class LodelDataHandlerConsistencyException(LodelDataHandlerException):
5 6
     pass

+ 39
- 34
lodel/leapi/datahandlers/references.py View File

@@ -3,35 +3,37 @@
3 3
 from lodel.context import LodelContext
4 4
 LodelContext.expose_modules(globals(), {
5 5
     'lodel.leapi.datahandlers.base_classes': ['Reference', 'MultipleRef',
6
-        'SingleRef'],
6
+                                              'SingleRef'],
7 7
     'lodel.logger': 'logger',
8 8
     'lodel.exceptions': ['LodelException', 'LodelExceptions',
9
-        'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
9
+                         'LodelFatalError', 'DataNoneValid',
10
+                         'FieldValidationError']})
11
+
10 12
 
11 13
 class Link(SingleRef):
12 14
     pass
13 15
 
14 16
 
15
-##@brief Child class of MultipleRef where references are represented in the form of a python list
17
+## @brief Child class of MultipleRef where references are represented in the form of a python list
16 18
 class List(MultipleRef):
17 19
 
18
-    ##@brief instanciates a list reference
20
+    ## @brief instanciates a list reference
19 21
     # @param max_length int
20 22
     # @param kwargs
21 23
     #   - allowed_classes list | None : list of allowed em classes if None no restriction
22 24
     #   - internal bool
23 25
 
24
-    def __init__(self, max_length = None, **kwargs):
26
+    def __init__(self, max_length=None, **kwargs):
25 27
         super().__init__(**kwargs)
26 28
 
27 29
     @classmethod
28 30
     def empty(cls):
29 31
         return list()
30 32
 
31
-    ##@brief Check and cast value in appropriate type
32
-    #@param value *
33
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
34
-    #@return value
33
+    ## @brief Check and cast value in appropriate type
34
+    # @param value *
35
+    # @throw FieldValidationError if value is unappropriate or can not be cast
36
+    # @return value
35 37
     def _check_data_value(self, value):
36 38
         value = super()._check_data_value(value)
37 39
         try:
@@ -39,12 +41,12 @@ class List(MultipleRef):
39 41
         except Exception as e:
40 42
             raise FieldValidationError("Given iterable is not castable in \
41 43
 a list : %s" % e)
42
-        return value
43 44
 
44
-##@brief Child class of MultipleRef where references are represented in the form of a python set
45
+
46
+## @brief Child class of MultipleRef where references are represented in the form of a python set
45 47
 class Set(MultipleRef):
46 48
 
47
-    ##@brief instanciates a set reference
49
+    ## @brief instanciates a set reference
48 50
     # @param kwargs : named arguments
49 51
     #   - allowed_classes list | None : list of allowed em classes if None no restriction
50 52
     #   - internal bool : if False, the field is not internal
@@ -55,10 +57,10 @@ class Set(MultipleRef):
55 57
     def empty(cls):
56 58
         return set()
57 59
 
58
-    ##@brief Check and cast value in appropriate type
59
-    #@param value *
60
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
61
-    #@return value
60
+    ## @brief Check and cast value in appropriate type
61
+    # @param value *
62
+    # @throw FieldValidationError if value is unappropriate or can not be cast
63
+    # @return value
62 64
     def _check_data_value(self, value):
63 65
         value = super()._check_data_value(value)
64 66
         try:
@@ -66,11 +68,12 @@ class Set(MultipleRef):
66 68
         except Exception as e:
67 69
             raise FieldValidationError("Given iterable is not castable in \
68 70
 a set : %s" % e)
69
-    
70
-##@brief Child class of MultipleRef where references are represented in the form of a python dict
71
+
72
+
73
+## @brief Child class of MultipleRef where references are represented in the form of a python dict
71 74
 class Map(MultipleRef):
72 75
 
73
-    ##@brief instanciates a dict reference
76
+    ## @brief instanciates a dict reference
74 77
     # @param kwargs : named arguments
75 78
     #   - allowed_classes list | None : list of allowed em classes if None no restriction
76 79
     #   - internal bool : if False, the field is not internal
@@ -81,38 +84,40 @@ class Map(MultipleRef):
81 84
     def empty(cls):
82 85
         return dict()
83 86
 
84
-    ##@brief Check and cast value in appropriate type
85
-    #@param value *
86
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
87
-    #@return value
87
+    ## @brief Check and cast value in appropriate type
88
+    # @param value *
89
+    # @throw FieldValidationError if value is unappropriate or can not be cast
90
+    # @return value
88 91
     def _check_data_value(self, value):
89 92
         value = super()._check_data_value(value)
90 93
         if not isinstance(value, dict):
91 94
             raise FieldValidationError("Values for dict fields should be dict")
92 95
         return value
93 96
 
94
-##@brief This Reference class is designed to handler hierarchy with some constraint
97
+
98
+## @brief This Reference class is designed to handler hierarchy with some constraint
95 99
 class Hierarch(MultipleRef):
96 100
     
97 101
     directly_editable = False
98
-    ##@brief Instanciate a data handler handling hierarchical relation with constraints
102
+
103
+    ## @brief Instanciate a data handler handling hierarchical relation with constraints
99 104
     # @param back_reference tuple : Here it is mandatory to have a back ref (like a parent field)
100 105
     # @param max_depth int | None :  limit of depth
101 106
     # @param max_childs int | Nine : maximum number of childs by nodes
102
-    def __init__(self, back_reference, max_depth = None, max_childs = None, **kwargs):
103
-        super().__init__(   back_reference = back_reference,
104
-                            max_depth = max_depth,
105
-                            max_childs = max_childs,
106
-                            **kwargs)
107
+    def __init__(self, back_reference, max_depth=None, max_childs=None, **kwargs):
108
+        super().__init__(back_reference=back_reference,
109
+                         max_depth=max_depth,
110
+                         max_childs=max_childs,
111
+                         **kwargs)
107 112
 
108 113
     @classmethod
109 114
     def empty(cls):
110 115
         return tuple()
111 116
 
112
-    ##@brief Check and cast value in appropriate type
113
-    #@param value *
114
-    #@throw FieldValidationError if value is unappropriate or can not be cast 
115
-    #@return value
117
+    ## @brief Check and cast value in appropriate type
118
+    # @param value *
119
+    # @throw FieldValidationError if value is unappropriate or can not be cast
120
+    # @return value
116 121
     def _check_data_value(self, value):
117 122
         value = super()._check_data_value(value)
118 123
         if  not (isinstance(value, list) or isinstance(value, str)):

+ 163
- 160
lodel/leapi/leobject.py View File

@@ -11,77 +11,79 @@ LodelContext.expose_modules(globals(), {
11 11
     'lodel.settings': 'Settings',
12 12
     'lodel.settings.utils': 'SettingsError',
13 13
     'lodel.leapi.query': ['LeInsertQuery', 'LeUpdateQuery', 'LeDeleteQuery',
14
-        'LeGetQuery'],
14
+                          'LeGetQuery'],
15 15
     'lodel.leapi.exceptions': ['LeApiError', 'LeApiErrors',
16
-        'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
17
-        'LeApiQueryErrors'],
16
+                               'LeApiDataCheckError', 'LeApiDataCheckErrors', 'LeApiQueryError',
17
+                               'LeApiQueryErrors'],
18 18
     'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
19
-        'LodelScriptError', 'DatasourcePluginError'],
19
+                                'LodelScriptError', 'DatasourcePluginError'],
20 20
     'lodel.exceptions': ['LodelFatalError'],
21 21
     'lodel.plugin.hooks': ['LodelHook'],
22 22
     'lodel.plugin': ['Plugin', 'DatasourcePlugin'],
23 23
     'lodel.leapi.datahandlers.base_classes': ['DatasConstructor', 'Reference']})
24 24
 
25
-##@brief Stores the name of the field present in each LeObject that indicates
26
-#the name of LeObject subclass represented by this object
25
+# @brief Stores the name of the field present in each LeObject that indicates
26
+# the name of LeObject subclass represented by this object
27 27
 CLASS_ID_FIELDNAME = "classname"
28 28
 
29
-##@brief Wrapper class for LeObject getter & setter
29
+# @brief Wrapper class for LeObject getter & setter
30 30
 #
31
-# This class intend to provide easy & friendly access to LeObject fields values 
31
+# This class intend to provide easy & friendly access to LeObject fields values
32 32
 # without name collision problems
33 33
 # @note Wrapped methods are : LeObject.data() & LeObject.set_data()
34
+
35
+
34 36
 class LeObjectValues(object):
35
-    
36
-    ##@brief Construct a new LeObjectValues
37
+
38
+    # @brief Construct a new LeObjectValues
37 39
     # @param fieldnames_callback method
38 40
     # @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
39 41
     # @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
40 42
     def __init__(self, fieldnames_callback, set_callback, get_callback):
41 43
         self._setter = set_callback
42 44
         self._getter = get_callback
43
-    
44
-    ##@brief Provide read access to datas values
45
+
46
+    # @brief Provide read access to datas values
45 47
     # @note Read access should be provided for all fields
46 48
     # @param fname str : Field name
47 49
     def __getattribute__(self, fname):
48 50
         getter = super().__getattribute__('_getter')
49 51
         return getter(fname)
50
-    
51
-    ##@brief Provide write access to datas values
52
+
53
+    # @brief Provide write access to datas values
52 54
     # @note Write acces shouldn't be provided for internal or immutable fields
53 55
     # @param fname str : Field name
54 56
     # @param fval * : the field value
55 57
     def __setattribute__(self, fname, fval):
56 58
         setter = super().__getattribute__('_setter')
57 59
         return setter(fname, fval)
58
-        
60
+
59 61
 
60 62
 class LeObject(object):
61
- 
62
-    ##@brief boolean that tells if an object is abtract or not
63
+
64
+    # @brief boolean that tells if an object is abtract or not
63 65
     _abstract = None
64
-    ##@brief A dict that stores DataHandler instances indexed by field name
66
+    # @brief A dict that stores DataHandler instances indexed by field name
65 67
     _fields = None
66
-    ##@brief A tuple of fieldname (or a uniq fieldname) representing uid
67
-    _uid = None 
68
-    ##@brief Read only datasource ( see @ref lodel2_datasources )
68
+    # @brief A tuple of fieldname (or a uniq fieldname) representing uid
69
+    _uid = None
70
+    # @brief Read only datasource ( see @ref lodel2_datasources )
69 71
     _ro_datasource = None
70
-    ##@brief Read & write datasource ( see @ref lodel2_datasources )
72
+    # @brief Read & write datasource ( see @ref lodel2_datasources )
71 73
     _rw_datasource = None
72
-    ##@brief Store the list of child classes
74
+    # @brief Store the list of child classes
73 75
     _child_classes = None
74
-    ##@brief Name of the datasource plugin
76
+    # @brief Name of the datasource plugin
75 77
     _datasource_name = None
76 78
 
77 79
     def __new__(cls, **kwargs):
78
-        
80
+
79 81
         self = object.__new__(cls)
80
-        ##@brief A dict that stores fieldvalues indexed by fieldname
81
-        self.__datas = { fname:None for fname in self._fields }
82
-        ##@brief Store a list of initianilized fields when instanciation not complete else store True
82
+        # @brief A dict that stores fieldvalues indexed by fieldname
83
+        self.__datas = {fname: None for fname in self._fields}
84
+        # @brief Store a list of initianilized fields when instanciation not complete else store True
83 85
         self.__initialized = list()
84
-        ##@brief Datas accessor. Instance of @ref LeObjectValues
86
+        # @brief Datas accessor. Instance of @ref LeObjectValues
85 87
         self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
86 88
         for fieldname, fieldval in kwargs.items():
87 89
             self.__datas[fieldname] = fieldval
@@ -90,11 +92,12 @@ class LeObject(object):
90 92
         self.__set_initialized()
91 93
         return self
92 94
 
93
-    ##@brief Construct an object representing an Editorial component
95
+    # @brief Construct an object representing an Editorial component
94 96
     # @note Can be considered as EmClass instance
95 97
     def __init__(self, **kwargs):
96 98
         if self._abstract:
97
-            raise NotImplementedError("%s is abstract, you cannot instanciate it." % self.__class__.__name__ )
99
+            raise NotImplementedError(
100
+                "%s is abstract, you cannot instanciate it." % self.__class__.__name__)
98 101
 
99 102
         # Checks that uid is given
100 103
         for uid_name in self._uid:
@@ -105,7 +108,7 @@ class LeObject(object):
105 108
             self.__initialized.append(uid_name)
106 109
 
107 110
         # Processing given fields
108
-        allowed_fieldnames = self.fieldnames(include_ro = False)
111
+        allowed_fieldnames = self.fieldnames(include_ro=False)
109 112
         err_list = dict()
110 113
         for fieldname, fieldval in kwargs.items():
111 114
             if fieldname not in allowed_fieldnames:
@@ -119,39 +122,39 @@ class LeObject(object):
119 122
                 self.__datas[fieldname] = fieldval
120 123
                 self.__initialized.append(fieldname)
121 124
         if len(err_list) > 0:
122
-            raise LeApiErrors(msg = "Unable to __init__ %s" % self.__class__,
123
-                exceptions = err_list)
125
+            raise LeApiErrors(msg="Unable to __init__ %s" % self.__class__,
126
+                              exceptions=err_list)
124 127
         self.__set_initialized()
125
-    
128
+
126 129
     #-----------------------------------#
127 130
     #   Fields datas handling methods   #
128 131
     #-----------------------------------#
129 132
 
130
-    ##@brief Property method True if LeObject is initialized else False
133
+    # @brief Property method True if LeObject is initialized else False
131 134
     @property
132 135
     def initialized(self):
133 136
         return self.__is_initialized
134
-    
135
-    ##@return The uid field name
137
+
138
+    # @return The uid field name
136 139
     @classmethod
137 140
     def uid_fieldname(cls):
138 141
         return cls._uid
139 142
 
140
-    ##@brief Return a list of fieldnames
143
+    # @brief Return a list of fieldnames
141 144
     # @param include_ro bool : if True include read only field names
142 145
     # @return a list of str
143 146
     @classmethod
144
-    def fieldnames(cls, include_ro = False):
147
+    def fieldnames(cls, include_ro=False):
145 148
         if not include_ro:
146
-            return [ fname for fname in cls._fields if not cls._fields[fname].is_internal() ]
149
+            return [fname for fname in cls._fields if not cls._fields[fname].is_internal()]
147 150
         else:
148 151
             return list(cls._fields.keys())
149
- 
152
+
150 153
     @classmethod
151 154
     def name2objname(cls, name):
152 155
         return name.title()
153
-    
154
-    ##@brief Return the datahandler asssociated with a LeObject field
156
+
157
+    # @brief Return the datahandler asssociated with a LeObject field
155 158
     # @param fieldname str : The fieldname
156 159
     # @return A data handler instance
157 160
     #@todo update class of exception raised
@@ -160,18 +163,18 @@ class LeObject(object):
160 163
         if not fieldname in cls._fields:
161 164
             raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
162 165
         return cls._fields[fieldname]
163
-    
164
-    ##@brief Getter for references datahandlers
166
+
167
+    # @brief Getter for references datahandlers
165 168
     #@param with_backref bool : if true return only references with back_references
166 169
     #@return <code>{'fieldname': datahandler, ...}</code>
167 170
     @classmethod
168
-    def reference_handlers(cls, with_backref = True):
169
-        return {    fname: fdh 
170
-                    for fname, fdh in cls.fields(True).items()
171
-                    if fdh.is_reference() and \
172
-                        (not with_backref or fdh.back_reference is not None)}
173
-    
174
-    ##@brief Return a LeObject child class from a name
171
+    def reference_handlers(cls, with_backref=True):
172
+        return {fname: fdh
173
+                for fname, fdh in cls.fields(True).items()
174
+                if fdh.is_reference() and
175
+                (not with_backref or fdh.back_reference is not None)}
176
+
177
+    # @brief Return a LeObject child class from a name
175 178
     # @warning This method has to be called from dynamically generated LeObjects
176 179
     # @param leobject_name str : LeObject name
177 180
     # @return A LeObject child class
@@ -183,14 +186,14 @@ class LeObject(object):
183 186
         mod = importlib.import_module(cls.__module__)
184 187
         try:
185 188
             return getattr(mod, leobject_name)
186
-        except (AttributeError, TypeError) :
189
+        except (AttributeError, TypeError):
187 190
             raise LeApiError("No LeObject named '%s'" % leobject_name)
188
-    
191
+
189 192
     @classmethod
190 193
     def is_abstract(cls):
191 194
         return cls._abstract
192
-    
193
-    ##@brief Field data handler getter
195
+
196
+    # @brief Field data handler getter
194 197
     #@param fieldname str : The field name
195 198
     #@return A datahandler instance
196 199
     #@throw NameError if the field doesn't exist
@@ -199,20 +202,22 @@ class LeObject(object):
199 202
         try:
200 203
             return cls._fields[fieldname]
201 204
         except KeyError:
202
-            raise NameError("No field named '%s' in %s" % ( fieldname,
203
-                                                            cls.__name__))
204
-    ##@return A dict with fieldname as key and datahandler as instance
205
+            raise NameError("No field named '%s' in %s" % (fieldname,
206
+                                                           cls.__name__))
207
+    # @return A dict with fieldname as key and datahandler as instance
208
+
205 209
     @classmethod
206
-    def fields(cls, include_ro = False):
210
+    def fields(cls, include_ro=False):
207 211
         if include_ro:
208 212
             return copy.copy(cls._fields)
209 213
         else:
210
-            return {fname:cls._fields[fname] for fname in cls._fields if not cls._fields[fname].is_internal()}
211
-    
212
-    ##@brief Return the list of parents classes
214
+            return {fname: cls._fields[fname] for fname in cls._fields\
215
+                    if not cls._fields[fname].is_internal()}
216
+
217
+    # @brief Return the list of parents classes
213 218
     #
214 219
     #@note the first item of the list is the current class, the second is it's
215
-    #parent etc...
220
+    # parent etc...
216 221
     #@param cls
217 222
     #@warning multiple inheritance broken by this method
218 223
     #@return a list of LeObject child classes
@@ -222,23 +227,22 @@ class LeObject(object):
222 227
         res = [cls]
223 228
         cur = cls
224 229
         while True:
225
-            cur = cur.__bases__[0] # Multiple inheritance broken HERE
230
+            cur = cur.__bases__[0]  # Multiple inheritance broken HERE
226 231
             if cur in (LeObject, object):
227 232
                 break
228 233
             else:
229 234
                 res.append(cur)
230 235
         return res
231
-    
232
-    ##@brief Return a tuple a child classes
236
+
237
+    # @brief Return a tuple a child classes
233 238
     #@return a tuple of child classes
234 239
     @classmethod
235 240
     def child_classes(cls):
236 241
         return copy.copy(cls._child_classes)
237
-        
238 242
 
239
-    ##@brief Return the parent class that is the "source" of uid
243
+    # @brief Return the parent class that is the "source" of uid
240 244
     #
241
-    #The method goal is to return the parent class that defines UID.
245
+    # The method goal is to return the parent class that defines UID.
242 246
     #@return a LeObject child class or false if no UID defined
243 247
     @classmethod
244 248
     def uid_source(cls):
@@ -246,19 +250,19 @@ class LeObject(object):
246 250
             return False
247 251
         hierarch = cls.hierarch()
248 252
         prev = hierarch[0]
249
-        uid_handlers = set( cls._fields[name] for name in cls._uid )
253
+        uid_handlers = set(cls._fields[name] for name in cls._uid)
250 254
         for pcls in cls.hierarch()[1:]:
251 255
             puid_handlers = set(cls._fields[name] for name in pcls._uid)
252 256
             if set(pcls._uid) != set(prev._uid) \
253
-                or puid_handlers != uid_handlers:
257
+                    or puid_handlers != uid_handlers:
254 258
                 break
255 259
             prev = pcls
256 260
         return prev
257
-    
258
-    ##@brief Initialise both datasources (ro and rw)
261
+
262
+    # @brief Initialise both datasources (ro and rw)
259 263
     #
260
-    #This method is used once at dyncode load to replace the datasource string
261
-    #by a datasource instance to avoid doing this operation for each query
264
+    # This method is used once at dyncode load to replace the datasource string
265
+    # by a datasource instance to avoid doing this operation for each query
262 266
     #@see LeObject::_init_datasource()
263 267
     @classmethod
264 268
     def _init_datasources(cls):
@@ -266,7 +270,7 @@ class LeObject(object):
266 270
             rw_ds = ro_ds = cls._datasource_name
267 271
         else:
268 272
             ro_ds, rw_ds = cls._datasource_name
269
-        #Read only datasource initialisation
273
+        # Read only datasource initialisation
270 274
         cls._ro_datasource = DatasourcePlugin.init_datasource(ro_ds, True)
271 275
         if cls._ro_datasource is None:
272 276
             log_msg = "No read only datasource set for LeObject %s"
@@ -276,7 +280,7 @@ class LeObject(object):
276 280
             log_msg = "Read only datasource '%s' initialized for LeObject %s"
277 281
             log_msg %= (ro_ds, cls.__name__)
278 282
             logger.debug(log_msg)
279
-        #Read write datasource initialisation
283
+        # Read write datasource initialisation
280 284
         cls._rw_datasource = DatasourcePlugin.init_datasource(rw_ds, False)
281 285
         if cls._ro_datasource is None:
282 286
             log_msg = "No read/write datasource set for LeObject %s"
@@ -286,14 +290,14 @@ class LeObject(object):
286 290
             log_msg = "Read/write datasource '%s' initialized for LeObject %s"
287 291
             log_msg %= (ro_ds, cls.__name__)
288 292
             logger.debug(log_msg)
289
-        
290
-    ##@brief Return the uid of the current LeObject instance
293
+
294
+    # @brief Return the uid of the current LeObject instance
291 295
     #@return the uid value
292 296
     #@warning Broke multiple uid capabilities
293 297
     def uid(self):
294 298
         return self.data(self._uid[0])
295 299
 
296
-    ##@brief Read only access to all datas
300
+    # @brief Read only access to all datas
297 301
     # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
298 302
     # @param field_name str : field name
299 303
     # @return the Value
@@ -303,16 +307,16 @@ class LeObject(object):
303 307
         if field_name not in self._fields.keys():
304 308
             raise NameError("No such field in %s : %s" % (self.__class__.__name__, field_name))
305 309
         if not self.initialized and field_name not in self.__initialized:
306
-            raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
310
+            raise RuntimeError(
311
+                "The field %s is not initialized yet (and have no value)" % field_name)
307 312
         return self.__datas[field_name]
308
-    
309
-    ##@brief Read only access to all datas
313
+
314
+    # @brief Read only access to all datas
310 315
     #@return a dict representing datas of current instance
311
-    def datas(self, internal = False):
312
-        return {fname:self.data(fname) for fname in self.fieldnames(internal)}
313
-        
314
-    
315
-    ##@brief Datas setter
316
+    def datas(self, internal=False):
317
+        return {fname: self.data(fname) for fname in self.fieldnames(internal)}
318
+
319
+    # @brief Datas setter
316 320
     # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
317 321
     # @param fname str : field name
318 322
     # @param fval * : field value
@@ -320,7 +324,7 @@ class LeObject(object):
320 324
     # @throw NameError if fname is not valid
321 325
     # @throw AttributeError if the field is not writtable
322 326
     def set_data(self, fname, fval):
323
-        if fname not in self.fieldnames(include_ro = False):
327
+        if fname not in self.fieldnames(include_ro=False):
324 328
             if fname not in self._fields.keys():
325 329
                 raise NameError("No such field in %s : %s" % (self.__class__.__name__, fname))
326 330
             else:
@@ -342,23 +346,23 @@ class LeObject(object):
342 346
             # We skip full validation here because the LeObject is not fully initialized yet
343 347
             val, err = self._fields[fname].check_data_value(fval)
344 348
             if isinstance(err, Exception):
345
-                #Revert change to be in valid state
349
+                # Revert change to be in valid state
346 350
                 del(self.__datas[fname])
347 351
                 del(self.__initialized[-1])
348
-                raise LeApiErrors("Data check error", {fname:err})
352
+                raise LeApiErrors("Data check error", {fname: err})
349 353
             else:
350 354
                 self.__datas[fname] = val
351
-    
352
-    ##@brief Update the __initialized attribute according to LeObject internal state
355
+
356
+    # @brief Update the __initialized attribute according to LeObject internal state
353 357
     #
354 358
     # Check the list of initialized fields and set __initialized to True if all fields initialized
355 359
     def __set_initialized(self):
356 360
         if isinstance(self.__initialized, list):
357
-            expected_fields = self.fieldnames(include_ro = False) + self._uid
361
+            expected_fields = self.fieldnames(include_ro=False) + self._uid
358 362
             if set(expected_fields) == set(self.__initialized):
359 363
                 self.__is_initialized = True
360 364
 
361
-    ##@brief Designed to be called when datas are modified
365
+    # @brief Designed to be called when datas are modified
362 366
     #
363 367
     # Make different checks on the LeObject given it's state (fully initialized or not)
364 368
     # @return None if checks succeded else return an exception list
@@ -366,7 +370,7 @@ class LeObject(object):
366 370
         err_list = dict()
367 371
         if self.__initialized is True:
368 372
             # Data value check
369
-            for fname in self.fieldnames(include_ro = False):
373
+            for fname in self.fieldnames(include_ro=False):
370 374
                 val, err = self._fields[fname].check_data_value(self.__datas[fname])
371 375
                 if err is not None:
372 376
                     err_list[fname] = err
@@ -374,19 +378,19 @@ class LeObject(object):
374 378
                     self.__datas[fname] = val
375 379
             # Data construction
376 380
             if len(err_list) == 0:
377
-                for fname in self.fieldnames(include_ro = True):
381
+                for fname in self.fieldnames(include_ro=True):
378 382
                     try:
379 383
                         field = self._fields[fname]
380
-                        self.__datas[fname] = field.construct_data(    self,
381
-                                                                        fname,
382
-                                                                        self.__datas,
383
-                                                                        self.__datas[fname]
384
-                        )
384
+                        self.__datas[fname] = field.construct_data(self,
385
+                                                                   fname,
386
+                                                                   self.__datas,
387
+                                                                   self.__datas[fname]
388
+                                                                   )
385 389
                     except Exception as exp:
386 390
                         err_list[fname] = exp
387 391
             # Datas consistency check
388 392
             if len(err_list) == 0:
389
-                for fname in self.fieldnames(include_ro = True):
393
+                for fname in self.fieldnames(include_ro=True):
390 394
                     field = self._fields[fname]
391 395
                     ret = field.check_data_consistency(self, fname, self.__datas)
392 396
                     if isinstance(ret, Exception):
@@ -404,8 +408,8 @@ class LeObject(object):
404 408
     #--------------------#
405 409
     #   Other methods    #
406 410
     #--------------------#
407
-    
408
-    ##@brief Temporary method to set private fields attribute at dynamic code generation
411
+
412
+    # @brief Temporary method to set private fields attribute at dynamic code generation
409 413
     #
410 414
     # This method is used in the generated dynamic code to set the _fields attribute
411 415
     # at the end of the dyncode parse
@@ -415,8 +419,8 @@ class LeObject(object):
415 419
     @classmethod
416 420
     def _set__fields(cls, field_list):
417 421
         cls._fields = field_list
418
-        
419
-    ## @brief Check that datas are valid for this type
422
+
423
+    # @brief Check that datas are valid for this type
420 424
     # @param datas dict : key == field name value are field values
421 425
     # @param complete bool : if True expect that datas provide values for all non internal fields
422 426
     # @param allow_internal bool : if True don't raise an error if a field is internal
@@ -424,10 +428,10 @@ class LeObject(object):
424 428
     # @return Checked datas
425 429
     # @throw LeApiDataCheckError if errors reported during check
426 430
     @classmethod
427
-    def check_datas_value(cls, datas, complete = False, allow_internal = True):
428
-        err_l = dict() #Error storing
429
-        correct = set() #valid fields name
430
-        mandatory = set() #mandatory fields name
431
+    def check_datas_value(cls, datas, complete=False, allow_internal=True):
432
+        err_l = dict()  # Error storing
433
+        correct = set()  # valid fields name
434
+        mandatory = set()  # mandatory fields name
431 435
         for fname, datahandler in cls._fields.items():
432 436
             if allow_internal or not datahandler.is_internal():
433 437
                 correct.add(fname)
@@ -436,15 +440,15 @@ class LeObject(object):
436 440
         provided = set(datas.keys())
437 441
         # searching for unknow fields
438 442
         for u_f in provided - correct:
439
-            #Here we can check if the field is invalid or rejected because
443
+            # Here we can check if the field is invalid or rejected because
440 444
             # it is internel
441 445
             err_l[u_f] = AttributeError("Unknown or unauthorized field '%s'" % u_f)
442 446
         # searching for missing mandatory fieldsa
443 447
         for missing in mandatory - provided:
444 448
             err_l[missing] = AttributeError("The data for field '%s' is missing" % missing)
445
-        #Checks datas
449
+        # Checks datas
446 450
         checked_datas = dict()
447
-        for name, value in [ (name, value) for name, value in datas.items() if name in correct ]:
451
+        for name, value in [(name, value) for name, value in datas.items() if name in correct]:
448 452
             dh = cls._fields[name]
449 453
             res = dh.check_data_value(value)
450 454
             checked_datas[name], err = res
@@ -455,10 +459,10 @@ class LeObject(object):
455 459
             raise LeApiDataCheckErrors("Error while checking datas", err_l)
456 460
         return checked_datas
457 461
 
458
-    ##@brief Check and prepare datas
459
-    # 
462
+    # @brief Check and prepare datas
463
+    #
460 464
     # @warning when complete = False we are not able to make construct_datas() and _check_data_consistency()
461
-    # 
465
+    #
462 466
     # @param datas dict : {fieldname : fieldvalue, ...}
463 467
     # @param complete bool : If True you MUST give all the datas
464 468
     # @param allow_internal : Wether or not interal fields are expected in datas
@@ -478,7 +482,7 @@ construction and consitency when datas are not complete\n")
478 482
             cls._check_datas_consistency(ret_datas)
479 483
         return ret_datas
480 484
 
481
-    ## @brief Construct datas values
485
+    # @brief Construct datas values
482 486
     #
483 487
     # @param cls
484 488
     # @param datas dict : Datas that have been returned by LeCrud.check_datas_value() methods
@@ -488,13 +492,13 @@ construction and consitency when datas are not complete\n")
488 492
     def _construct_datas(cls, datas):
489 493
         constructor = DatasConstructor(cls, datas, cls._fields)
490 494
         ret = {
491
-                fname:constructor[fname]
492
-                for fname, ftype in cls._fields.items()
493
-                if not ftype.is_internal() or ftype.internal != 'autosql'
495
+            fname: constructor[fname]
496
+            for fname, ftype in cls._fields.items()
497
+            if not ftype.is_internal() or ftype.internal != 'autosql'
494 498
         }
495 499
         return ret
496
-    
497
-    ## @brief Check datas consistency
500
+
501
+    # @brief Check datas consistency
498 502
499 503
     # @warning assert that datas is complete
500 504
     # @param cls
@@ -511,29 +515,29 @@ construction and consitency when datas are not complete\n")
511 515
 
512 516
         if len(err_l) > 0:
513 517
             raise LeApiDataCheckError("Datas consistency checks fails", err_l)
514
-    
515
-    ## @brief Check datas consistency
518
+
519
+    # @brief Check datas consistency
516 520
517 521
     # @warning assert that datas is complete
518 522
     # @param cls
519 523
     # @param datas dict : Datas that have been returned by LeCrud.prepare_datas() method
520 524
     # @param type_query str : Type of query to be performed , default value : insert
521 525
     @classmethod
522
-    def make_consistency(cls, datas, type_query = 'insert'):
526
+    def make_consistency(cls, datas, type_query='insert'):
523 527
         for fname, dh in cls._fields.items():
524 528
             ret = dh.make_consistency(fname, datas, type_query)
525
-            
526
-    ## @brief Add a new instance of LeObject
529
+
530
+    # @brief Add a new instance of LeObject
527 531
     # @return a new uid en case of success, False otherwise
528 532
     @classmethod
529 533
     def insert(cls, datas):
530 534
         query = LeInsertQuery(cls)
531 535
         return query.execute(datas)
532 536
 
533
-    ## @brief Update an instance of LeObject
537
+    # @brief Update an instance of LeObject
534 538
     #
535
-    #@param datas : list of new datas 
536
-    def update(self, datas = None):
539
+    #@param datas : list of new datas
540
+    def update(self, datas=None):
537 541
         datas = self.datas(internal=False) if datas is None else datas
538 542
         uids = self._uid
539 543
         query_filter = list()
@@ -543,15 +547,15 @@ construction and consitency when datas are not complete\n")
543 547
             query = LeUpdateQuery(self.__class__, query_filter)
544 548
         except Exception as err:
545 549
             raise err
546
-            
550
+
547 551
         try:
548 552
             result = query.execute(datas)
549 553
         except Exception as err:
550 554
             raise err
551 555
 
552 556
         return result
553
-    
554
-    ## @brief Delete an instance of LeObject
557
+
558
+    # @brief Delete an instance of LeObject
555 559
     #
556 560
     #@return 1 if the objet has been deleted
557 561
     def delete(self):
@@ -565,8 +569,8 @@ construction and consitency when datas are not complete\n")
565 569
         result = query.execute()
566 570
 
567 571
         return result
568
-    
569
-    ## @brief Delete instances of LeObject
572
+
573
+    # @brief Delete instances of LeObject
570 574
     #@param query_filters list
571 575
     #@returns the number of deleted items
572 576
     @classmethod
@@ -576,7 +580,7 @@ construction and consitency when datas are not complete\n")
576 580
             query = LeDeleteQuery(cls, query_filters)
577 581
         except Exception as err:
578 582
             raise err
579
-                
583
+
580 584
         try:
581 585
             result = query.execute()
582 586
         except Exception as err:
@@ -584,11 +588,11 @@ construction and consitency when datas are not complete\n")
584 588
         if not result is None:
585 589
             deleted += result
586 590
         return deleted
587
-            
588
-    ## @brief Get instances of LeObject
591
+
592
+    # @brief Get instances of LeObject
589 593
     #
590 594
     #@param query_filters dict : (filters, relational filters), with filters is a list of tuples : (FIELD, OPERATOR, VALUE) )
591
-    #@param field_list list|None : list of string representing fields see 
595
+    #@param field_list list|None : list of string representing fields see
592 596
     #@ref leobject_filters
593 597
     #@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
594 598
     #@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
@@ -598,49 +602,49 @@ construction and consitency when datas are not complete\n")
598 602
     @classmethod
599 603
     def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0):
600 604
         if field_list is not None:
601
-            for uid in [ uidname
602
-                for uidname in cls.uid_fieldname()
603
-                if uidname not in field_list ]:
605
+            for uid in [uidname
606
+                        for uidname in cls.uid_fieldname()
607
+                        if uidname not in field_list]:
604 608
                 field_list.append(uid)
605 609
             if CLASS_ID_FIELDNAME not in field_list:
606 610
                 field_list.append(CLASS_ID_FIELDNAME)
607 611
         try:
608 612
             query = LeGetQuery(
609
-                cls, query_filters = query_filters, field_list = field_list,
610
-                order = order, group = group, limit = limit, offset = offset)
613
+                cls, query_filters=query_filters, field_list=field_list,
614
+                order=order, group=group, limit=limit, offset=offset)
611 615
         except ValueError as err:
612 616
             raise err
613
-            
617
+
614 618
         try:
615 619
             result = query.execute()
616 620
         except Exception as err:
617 621
             raise err
618
-        
622
+
619 623
         objects = list()
620 624
         for res in result:
621 625
             res_cls = cls.name2class(res[CLASS_ID_FIELDNAME])
622
-            inst = res_cls.__new__(res_cls,**res)
626
+            inst = res_cls.__new__(res_cls, **res)
623 627
             objects.append(inst)
624
-        
628
+
625 629
         return objects
626
-    
627
-    ##@brief Retrieve an object given an UID
630
+
631
+    # @brief Retrieve an object given an UID
628 632
     #@todo broken multiple UID
629 633
     @classmethod
630 634
     def get_from_uid(cls, uid):
631 635
         if cls.uid_fieldname() is None:
632 636
             raise LodelFatalError(
633 637
                 "No uid defined for class %s" % cls.__name__)
634
-        uidname = cls.uid_fieldname()[0] #Brokes composed UID
635
-        res = cls.get([(uidname,'=', uid)])
636
-        
637
-        #dedoublonnage vu que query ou la datasource est bugué
638
+        uidname = cls.uid_fieldname()[0]  # Brokes composed UID
639
+        res = cls.get([(uidname, '=', uid)])
640
+
641
+        # dedoublonnage vu que query ou la datasource est bugué
638 642
         if len(res) > 1:
639 643
             res_cp = res
640 644
             res = []
641 645
             while len(res_cp) > 0:
642 646
                 cur_res = res_cp.pop()
643
-                if cur_res.uid() in [ r.uid() for r in res_cp]:
647
+                if cur_res.uid() in [r.uid() for r in res_cp]:
644 648
                     logger.error("DOUBLON detected in query results !!!")
645 649
                 else:
646 650
                     res.append(cur_res)
@@ -651,7 +655,7 @@ object ! For class %s with uid value = %s" % (cls, uid))
651 655
             return None
652 656
         return res[0]
653 657
 
654
-    ##@brief Checks if an object exists
658
+    # @brief Checks if an object exists
655 659
     @classmethod
656 660
     def is_exist(cls, uid):
657 661
         if cls.uid_fieldname() is None:
@@ -659,4 +663,3 @@ object ! For class %s with uid value = %s" % (cls, uid))
659 663
                 "No uid defined for class %s" % cls.__name__)
660 664
         from .query import is_exist
661 665
         return is_exist(cls, uid)
662
-

+ 2
- 0
lodel/mlnamedobject/Makefile.am View File

@@ -0,0 +1,2 @@
1
+mlnamedobject_PYTHON= *.py
2
+mlnamedobjectdir=$(pkgpythondir)/mlnamedobject

+ 0
- 0
lodel/mlnamedobject/__init__.py View File


+ 18
- 0
lodel/mlnamedobject/mlnamedobject.py View File

@@ -0,0 +1,18 @@
1
+#-*- coding:utf-8 -*-
2
+
3
+from lodel.context import LodelContext
4
+LodelContext.expose_modules(globals(), {
5
+    'lodel.utils.mlstring': ['MlString']})
6
+
7
+# @package lodel.mlnamedobject Lodel2 description of objects module
8
+#
9
+# Display name and Description of a lodel2 object
10
+
11
+# @brief Class allows display name and help text for lodel2 objects and fields
12
+
13
+
14
+class MlNamedObject(object):
15
+
16
+    def __init__(self, display_name=None, help_text=None):
17
+        self.display_name = None if display_name is None else MlString(display_name)
18
+        self.help_text = None if help_text is None else MlString(help_text)

+ 6
- 6
lodel/plugin/datasource_plugin.py View File

@@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
3 3
     'lodel.plugin.plugins': ['Plugin'],
4 4
     'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
5 5
         'LodelScriptError', 'DatasourcePluginError'],
6
-    'lodel.settings.validator': ['SettingValidator'],
6
+    'lodel.validator.validator': ['Validator'],
7 7
     'lodel.exceptions': ['LodelException', 'LodelExceptions',
8 8
         'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
9 9
 
@@ -97,7 +97,7 @@ class DatasourcePlugin(Plugin):
97 97
         'section': 'lodel2',
98 98
         'key': 'datasource_connectors',
99 99
         'default': 'dummy_datasource',
100
-        'validator': SettingValidator(
100
+        'validator': Validator(
101 101
             'custom_list', none_is_valid = False,
102 102
             validator_name = 'plugin', validator_kwargs = {
103 103
                 'ptype': _glob_typename,
@@ -280,13 +280,13 @@ but %s is a %s" % (ds_name, pinstance.__class__.__name__))
280 280
 #CONFSPEC = {
281 281
 #                'lodel2.datasource.mysql.*' : {
282 282
 #                    'host': (   'localhost',
283
-#                                SettingValidator('host')),
283
+#                                Validator('host')),
284 284
 #                    'db_name': (    'lodel',
285
-#                                    SettingValidator('string')),
285
+#                                    Validator('string')),
286 286
 #                    'username': (   None,
287
-#                                    SettingValidator('string')),
287
+#                                    Validator('string')),
288 288
 #                    'password': (   None,
289
-#                                    SettingValidator('string')),
289
+#                                    Validator('string')),
290 290
 #                }
291 291
 #}
292 292
 #</pre>

+ 2
- 2
lodel/plugin/extensions.py View File

@@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
3 3
     'lodel.plugin.plugins': ['Plugin'],
4 4
     'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
5 5
         'LodelScriptError', 'DatasourcePluginError'],
6
-    'lodel.settings.validator': ['SettingValidator']})
6
+    'lodel.validator.validator': ['Validator']})
7 7
 
8 8
 _glob_typename = 'extension'
9 9
 
@@ -14,7 +14,7 @@ class Extension(Plugin):
14 14
         'section': 'lodel2',
15 15
         'key': 'extensions',
16 16
         'default': None,
17
-        'validator': SettingValidator(
17
+        'validator': Validator(
18 18
             'custom_list', none_is_valid = True,
19 19
             validator_name = 'plugin', validator_kwargs = {
20 20
                 'ptype': _glob_typename,

+ 2
- 2
lodel/plugin/interface.py View File

@@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
3 3
     'lodel.plugin.plugins': ['Plugin'],
4 4
     'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
5 5
         'LodelScriptError', 'DatasourcePluginError'],
6
-    'lodel.settings.validator': ['SettingValidator']})
6
+    'lodel.validator.validator': ['Validator']})
7 7
 
8 8
 _glob_typename = 'ui'
9 9
 
@@ -19,7 +19,7 @@ class InterfacePlugin(Plugin):
19 19
         'section': 'lodel2',
20 20
         'key': 'interface',
21 21
         'default': None,
22
-        'validator': SettingValidator(
22
+        'validator': Validator(
23 23
             'plugin', none_is_valid = True, ptype = _glob_typename)}
24 24
 
25 25
     _type_conf_name = _glob_typename

+ 2
- 2
lodel/plugin/sessionhandler.py View File

@@ -3,7 +3,7 @@ LodelContext.expose_modules(globals(), {
3 3
     'lodel.plugin.plugins': ['Plugin', 'MetaPlugType'],
4 4
     'lodel.plugin.exceptions': ['PluginError', 'PluginTypeError',
5 5
         'LodelScriptError', 'DatasourcePluginError'],
6
-    'lodel.settings.validator': ['SettingValidator']})
6
+    'lodel.validator.validator': ['Validator']})
7 7
 
8 8
 
9 9
 ##@brief SessionHandlerPlugin metaclass designed to implements a wrapper
@@ -53,7 +53,7 @@ class SessionHandlerPlugin(Plugin, metaclass=SessionPluginWrapper):
53 53
         'section': 'lodel2',
54 54
         'key': 'session_handler',
55 55
         'default': None,
56
-        'validator': SettingValidator(
56
+        'validator': Validator(
57 57
             'plugin', none_is_valid=False,ptype = _glob_typename)}
58 58
 
59 59
     _type_conf_name = _glob_typename

+ 1
- 1
lodel/plugins/dummy/__init__.py View File

@@ -1,6 +1,6 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 
5 5
 __plugin_name__ = "dummy"
6 6
 __version__ = '0.0.1' #or __version__ = [0,0,1]

+ 2
- 2
lodel/plugins/dummy/confspec.py View File

@@ -2,11 +2,11 @@
2 2
 
3 3
 from lodel.context import LodelContext
4 4
 LodelContext.expose_modules(globals(), {
5
-    'lodel.settings.validator': ['SettingValidator']})
5
+    'lodel.validator.validator': ['Validator']})
6 6
 
7 7
 CONFSPEC = {
8 8
     'lodel2.section1': {
9 9
         'key1': (   None,
10
-                    SettingValidator('dummy'))
10
+                    Validator('dummy'))
11 11
     }
12 12
 }

+ 2
- 2
lodel/plugins/dummy_datasource/__init__.py View File

@@ -1,6 +1,6 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 from .datasource import DummyDatasource as Datasource
5 5
 
6 6
 __plugin_type__ = 'datasource'
@@ -12,7 +12,7 @@ __plugin_deps__ = []
12 12
 CONFSPEC = {
13 13
     'lodel2.datasource.dummy_datasource.*' : {
14 14
         'dummy': (  None,
15
-                    SettingValidator('dummy'))}
15
+                    Validator('dummy'))}
16 16
 }
17 17
 
18 18
 

+ 1
- 1
lodel/plugins/filesystem_session/__init__.py View File

@@ -1,6 +1,6 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 
5 5
 __plugin_name__ = 'filesystem_session'
6 6
 __version__ = [0,0,1]

+ 4
- 4
lodel/plugins/filesystem_session/confspec.py View File

@@ -2,12 +2,12 @@
2 2
 
3 3
 from lodel.context import LodelContext
4 4
 LodelContext.expose_modules(globals(), {
5
-    'lodel.settings.validator': ['SettingValidator']})
5
+    'lodel.validator.validator': ['Validator']})
6 6
 
7 7
 CONFSPEC = {
8 8
     'lodel2.sessions':{
9
-        'directory': ('/tmp/', SettingValidator('path')),
10
-        'expiration': (900, SettingValidator('int')),
11
-        'file_template': ('lodel2_%s.sess', SettingValidator('dummy'))
9
+        'directory': ('/tmp/', Validator('path')),
10
+        'expiration': (900, Validator('int')),
11
+        'file_template': ('lodel2_%s.sess', Validator('dummy'))
12 12
     }
13 13
 }

+ 7
- 7
lodel/plugins/mongodb_datasource/confspec.py View File

@@ -2,7 +2,7 @@
2 2
 
3 3
 from lodel.context import LodelContext
4 4
 LodelContext.expose_modules(globals(), {
5
-    'lodel.settings.validator': ['SettingValidator']})
5
+    'lodel.validator.validator': ['Validator']})
6 6
 
7 7
 ##@brief Mongodb datasource plugin confspec
8 8
 #@ingroup plugin_mongodb_datasource
@@ -10,11 +10,11 @@ LodelContext.expose_modules(globals(), {
10 10
 #Describe mongodb plugin configuration. Keys are :
11 11
 CONFSPEC = {
12 12
     'lodel2.datasource.mongodb_datasource.*':{
13
-        'read_only': (False, SettingValidator('bool')),
14
-        'host': ('localhost', SettingValidator('host')),
15
-        'port': (None, SettingValidator('string', none_is_valid = True)),
16
-        'db_name':('lodel', SettingValidator('string')),
17
-        'username': (None, SettingValidator('string')),
18
-        'password': (None, SettingValidator('string'))
13
+        'read_only': (False, Validator('bool')),
14
+        'host': ('localhost', Validator('host')),
15
+        'port': (None, Validator('string', none_is_valid = True)),
16
+        'db_name':('lodel', Validator('string')),
17
+        'username': (None, Validator('string')),
18
+        'password': (None, Validator('string'))
19 19
     }
20 20
 }

+ 3
- 3
lodel/plugins/multisite/__init__.py View File

@@ -1,7 +1,7 @@
1 1
 from lodel.context import LodelContext, ContextError
2 2
 try:
3 3
     LodelContext.expose_modules(globals(), {
4
-        'lodel.settings.validator': ['SettingValidator']})
4
+        'lodel.validator.validator': ['Validator']})
5 5
 
6 6
     __plugin_name__ = "multisite"
7 7
     __version__ = '0.0.1' #or __version__ = [0,0,1]
@@ -13,8 +13,8 @@ try:
13 13
 
14 14
     CONFSPEC = {
15 15
         'lodel2.server': {
16
-            'port': (80,SettingValidator('int')),
17
-            'listen_addr': ('', SettingValidator('string')),
16
+            'port': (80,Validator('int')),
17
+            'listen_addr': ('', Validator('string')),
18 18
         }
19 19
     }
20 20
 

+ 15
- 15
lodel/plugins/multisite/confspecs.py View File

@@ -1,34 +1,34 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 
5 5
 #Define a minimal confspec used by multisite loader
6 6
 LODEL2_CONFSPECS = {
7 7
     'lodel2': {
8
-        'debug': (True, SettingValidator('bool'))
8
+        'debug': (True, Validator('bool'))
9 9
     },
10 10
     'lodel2.server': {
11
-        'listen_address': ('127.0.0.1', SettingValidator('dummy')),
12
-        #'listen_address': ('', SettingValidator('ip')), #<-- not implemented
13
-        'listen_port': ( 1337, SettingValidator('int')),
14
-        'uwsgi_workers': (8, SettingValidator('int')),
15
-        'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
16
-        'virtualenv': (None, SettingValidator('path', none_is_valid = True)),
11
+        'listen_address': ('127.0.0.1', Validator('dummy')),
12
+        #'listen_address': ('', Validator('ip')), #<-- not implemented
13
+        'listen_port': ( 1337, Validator('int')),
14
+        'uwsgi_workers': (8, Validator('int')),
15
+        'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
16
+        'virtualenv': (None, Validator('path', none_is_valid = True)),
17 17
     },
18 18
     'lodel2.logging.*' : {
19 19
         'level': (  'ERROR',
20
-                    SettingValidator('loglevel')),
20
+                    Validator('loglevel')),
21 21
         'context': (    False,
22
-                        SettingValidator('bool')),
22
+                        Validator('bool')),
23 23
         'filename': (   None,
24
-                        SettingValidator('errfile', none_is_valid = True)),
24
+                        Validator('errfile', none_is_valid = True)),
25 25
         'backupcount': (    10,
26
-                            SettingValidator('int', none_is_valid = False)),
26
+                            Validator('int', none_is_valid = False)),
27 27
         'maxbytes': (   1024*10,
28
-                        SettingValidator('int', none_is_valid = False)),
28
+                        Validator('int', none_is_valid = False)),
29 29
     },
30 30
     'lodel2.datasources.*': {
31
-        'read_only': (False, SettingValidator('bool')),
32
-        'identifier': ( None, SettingValidator('string')),
31
+        'read_only': (False, Validator('bool')),
32
+        'identifier': ( None, Validator('string')),
33 33
     }
34 34
 }

+ 3
- 3
lodel/plugins/ram_sessions/__init__.py View File

@@ -1,6 +1,6 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 
5 5
 __plugin_name__ = 'ram_sessions'
6 6
 __version__ = [0,0,1]
@@ -11,7 +11,7 @@ __fullname__ = "RAM Session Store Plugin"
11 11
 
12 12
 CONFSPEC = {
13 13
     'lodel2.sessions':{
14
-        'expiration': (900, SettingValidator('int')),
15
-        'tokensize': (512, SettingValidator('int')),
14
+        'expiration': (900, Validator('int')),
15
+        'tokensize': (512, Validator('int')),
16 16
     }
17 17
 }

+ 13
- 13
lodel/plugins/webui/confspec.py View File

@@ -1,30 +1,30 @@
1 1
 from lodel.context import LodelContext
2 2
 LodelContext.expose_modules(globals(), {
3
-    'lodel.settings.validator': ['SettingValidator']})
3
+    'lodel.validator.validator': ['Validator']})
4 4
 
5 5
 CONFSPEC = {
6 6
     'lodel2.webui': {
7 7
         'standalone': ( 'False',
8
-                        SettingValidator('string')),
8
+                        Validator('string')),
9 9
         'listen_address': ( '127.0.0.1',
10
-                            SettingValidator('dummy')),
10
+                            Validator('dummy')),
11 11
         'listen_port': (    '9090',
12
-                            SettingValidator('int')),
12
+                            Validator('int')),
13 13
         'static_url': (     'http://127.0.0.1/static/',
14
-                            SettingValidator('regex', pattern =  r'^https?://[^/].*$')),
14
+                            Validator('regex', pattern =  r'^https?://[^/].*$')),
15 15
         'virtualenv': (None,
16
-                       SettingValidator('path', none_is_valid=True)),
17
-        'uwsgicmd': ('/usr/bin/uwsgi', SettingValidator('dummy')),
18
-        'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', SettingValidator('dummy')),
19
-        'cookie_session_id': ('lodel', SettingValidator('dummy')),
20
-        'uwsgi_workers': (2, SettingValidator('int'))
16
+                       Validator('path', none_is_valid=True)),
17
+        'uwsgicmd': ('/usr/bin/uwsgi', Validator('dummy')),
18
+        'cookie_secret_key': ('ConfigureYourOwnCookieSecretKey', Validator('dummy')),
19
+        'cookie_session_id': ('lodel', Validator('dummy')),
20
+        'uwsgi_workers': (2, Validator('int'))
21 21
     },
22 22
     'lodel2.webui.sessions': {
23 23
         'directory': (  '/tmp',
24
-                        SettingValidator('path')),
24
+                        Validator('path')),
25 25
         'expiration': ( 900,
26
-                        SettingValidator('int')),
26
+                        Validator('int')),
27 27
         'file_template': (  'lodel2_%s.sess',
28
-                            SettingValidator('dummy')),
28
+                            Validator('dummy')),
29 29
     }
30 30
 }

+ 8
- 8
lodel/plugins/webui/templates/listing/issue.html View File

@@ -4,7 +4,7 @@
4 4
 {% set objects = my_classes.Issue.get(('%s = %s') % (uidfield, lodel_id)) %}
5 5
 {% set person_class = leapi.name2class('Person') %}
6 6
 {% set obj = objects.pop() %}
7
-{% block content %} 
7
+{% block content %}
8 8
 <ol class="breadcrumb">
9 9
   <li><a href="/{{ root_url }}/">Home</a></li>
10 10
   <li><a href="/{{ root_url }}/collection">Collections</a></li>
@@ -13,14 +13,14 @@
13 13
 <h1 class="h1_lodel">Issue {{ obj.data('title') }} </h1>
14 14
 <h2>{{ obj.data('subtitle') }}</h2>
15 15
 {% set directors=person_class.get(("%s in (%s)") % (person_class.uid_fieldname()[0], obj.data('linked_directors')|join(','))) %}
16
-<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " target="_blank" >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
16
+<p><strong>Directors : </strong>{% for director in directors %} <a href="/{{ root_url }}/show_object?classname=Person&lodel_id={{ director.uid() }} " >{{ director.data('firstname')}} {{ director.data('lastname')}}</a> ; {% endfor %}</p>
17 17
 {% set texts=my_classes.Text.get(("%s in (%s)") % (my_classes.Text.uid_fieldname()[0], obj.data('linked_texts')|join(','))) %}
18 18
 {% set parts=my_classes.Part.get(("%s in (%s)") % (my_classes.Part.uid_fieldname()[0], obj.data('linked_parts')|join(','))) %}
19 19
 {% if texts is not none: %}
20 20
     <ul>
21 21
      {% for text in texts %}
22 22
           <li>
23
-              <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" target="_blank" > {{ text.data('title') }}</a></h3>
23
+              <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
24 24
               <h4>{{ text.data('subtitle') }}</h4>
25 25
               {% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
26 26
               <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@@ -34,7 +34,7 @@
34 34
 <ul>
35 35
      {% for part in parts %}
36 36
           <li>
37
-              <h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
37
+              <h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}"> {{ part.data('title') }}</a></h3>
38 38
               <h4>{{ part.data('subtitle') }}</h4>
39 39
               {% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
40 40
               <p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@@ -43,7 +43,7 @@
43 43
               <ul style="margin-left:20px">
44 44
                  {% for text in p_texts %}
45 45
                       <li>
46
-                          <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}"  target="_blank"> {{ text.data('title') }}</a></h3>
46
+                          <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
47 47
                           <h4>{{ text.data('subtitle') }}</h4>
48 48
                           {% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
49 49
                           <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@@ -57,7 +57,7 @@
57 57
                 <ul>
58 58
                      {% for part in ss_parts %}
59 59
                           <li>
60
-                              <h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" target="_blank"> {{ part.data('title') }}</a></h3>
60
+                              <h3><a href="/{{ root_url }}/show_object?classname={{ part.data('classname') }}&lodel_id={{ part.uid() }}" > {{ part.data('title') }}</a></h3>
61 61
                               <h4>{{ part.data('subtitle') }}</h4>
62 62
                               {% set directors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], part.data('linked_directors')|join(','))) %}
63 63
                               <p>Directors : {% for director in directors %} {{ director.data('firstname')}} {{ director.data('lastname')}} ; {% endfor %} </p>
@@ -66,7 +66,7 @@
66 66
                               <ul style="margin-left:20px">
67 67
                                  {% for text in sp_texts %}
68 68
                                       <li>
69
-                                          <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}"  target="_blank"> {{ text.data('title') }}</a></h3>
69
+                                          <h3><a href="/{{ root_url }}/show_object?classname={{ text.data('classname') }}&lodel_id={{ text.uid() }}" > {{ text.data('title') }}</a></h3>
70 70
                                           <h4>{{ text.data('subtitle') }}</h4>
71 71
                                           {% set authors = my_classes.Person.get(("%s in (%s)") % (person_class.uid_fieldname()[0], text.data('linked_persons')|join(','))) %}
72 72
                                           <p>Authors : {% for author in authors %} {{ author.data('firstname')}} {{ author.data('lastname')}} ; {% endfor %} </p>
@@ -84,4 +84,4 @@
84 84
 {% endif %}
85 85
 </div>
86 86
 
87
-{% endblock %} 
87
+{% endblock %}

+ 80
- 68
lodel/settings/settings.py View File

@@ -5,40 +5,42 @@ import os
5 5
 import configparser
6 6
 import copy
7 7
 import warnings
8
-import types # for dynamic bindings
8
+import types  # for dynamic bindings
9 9
 from collections import namedtuple
10 10
 
11 11
 from lodel.context import LodelContext
12 12
 
13
-LodelContext.expose_modules(globals(),{
13
+LodelContext.expose_modules(globals(), {
14 14
     'lodel.logger': 'logger',
15 15
     'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
16
-    'lodel.settings.validator': ['SettingValidator', 'LODEL2_CONF_SPECS',
17
-        'confspec_append'],
18
-    'lodel.settings.settings_loader':['SettingsLoader']})
19
-    
16
+    'lodel.validator.validator': ['Validator', 'LODEL2_CONF_SPECS',
17
+                                  'confspec_append'],
18
+    'lodel.settings.settings_loader': ['SettingsLoader']})
20 19
 
21
-## @package lodel.settings.settings Lodel2 settings module
20
+
21
+#  @package lodel.settings.settings Lodel2 settings module
22 22
 #
23 23
 # Contains the class that handles the namedtuple tree of settings
24 24
 
25
-##@brief A default python system lib path
25
+# @brief A default python system lib path
26 26
 PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
27 27
 
28
-                                                major = sys.version_info.major,
29
-                                                minor = sys.version_info.minor)
28
+    major=sys.version_info.major,
29
+    minor=sys.version_info.minor)
30
+
30 31
 
31 32
 class MetaSettings(type):
33
+
32 34
     @property
33 35
     def s(self):
34 36
         self.singleton_assert(True)
35 37
         return self.instance.settings
36 38
 
37
-##@brief Handles configuration load etc.
39
+# @brief Handles configuration load etc.
38 40
 #
39
-# To see howto bootstrap Settings and use it in lodel instance see 
41
+# To see howto bootstrap Settings and use it in lodel instance see
40 42
 # @ref lodel.settings
41
-# 
43
+#
42 44
 # @par Basic instance usage
43 45
 # For example if a file defines confs like :
44 46
 # <pre>
@@ -50,15 +52,15 @@ class MetaSettings(type):
50 52
 #
51 53
 # @par Init sequence
52 54
 # The initialization sequence is a bit tricky. In fact, plugins adds allowed
53
-# configuration sections/values, but the list of plugins to load are in... the 
55
+# configuration sections/values, but the list of plugins to load are in... the
54 56
 # settings.
55 57
 # Here is the conceptual presentation of Settings class initialization stages :
56 58
 #   -# Preloading (sets values like lodel2 library path or the plugins path)
57
-#   -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all 
58
-#configurations files
59
+#   -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all
60
+# configurations files
59 61
 #   -# Fetch the list of plugins in the loaded settings
60
-#   -# Merge plugins settings specification with the global lodel settings 
61
-#specs ( see @ref lodel.plugin )
62
+#   -# Merge plugins settings specification with the global lodel settings
63
+# specs ( see @ref lodel.plugin )
62 64
 #   -# Fetch all settings from the merged settings specs
63 65
 #
64 66
 # @par Init sequence in practical
@@ -68,39 +70,41 @@ class MetaSettings(type):
68 70
 #   -# @ref Settings.__populate_from_specs() (step 5)
69 71
 #   -# And finally @ref Settings.__confs_to_namedtuple()
70 72
 #
71
-# @todo handles default sections for variable sections (sections ending with 
73
+# @todo handles default sections for variable sections (sections ending with
72 74
 # '.*')
73 75
 # @todo delete the first stage, the lib path HAVE TO BE HARDCODED. In fact
74
-#when we will run lodel in production the lodel2 lib will be in the python path
76
+# when we will run lodel in production the lodel2 lib will be in the python path
75 77
 #@todo add log messages (now we can)
78
+
79
+
76 80
 class Settings(object, metaclass=MetaSettings):
77 81
 
78
-    ## @brief Stores the singleton instance
82
+    #  @brief Stores the singleton instance
79 83
     instance = None
80
-    
81
-    ## @brief Instanciate the Settings singleton
84
+
85
+    #  @brief Instanciate the Settings singleton
82 86
     # @param conf_dir str : The configuration directory
83 87
     #@param custom_confspecs None | dict : if given overwrite default lodel2
84
-    #confspecs
85
-    def __init__(self, conf_dir, custom_confspecs = None):
86
-        self.singleton_assert() # check that it is the only instance
88
+    # confspecs
89
+    def __init__(self, conf_dir, custom_confspecs=None):
90
+        self.singleton_assert()  # check that it is the only instance
87 91
         Settings.instance = self
88
-        ## @brief Configuration specification
92
+        #  @brief Configuration specification
89 93
         #
90 94
         # Initialized by Settings.__bootstrap() method
91 95
         self.__conf_specs = custom_confspecs
92
-        ## @brief Stores the configurations in namedtuple tree
96
+        #  @brief Stores the configurations in namedtuple tree
93 97
         self.__confs = None
94 98
         self.__conf_dir = conf_dir
95 99
         self.__started = False
96 100
         self.__bootstrap()
97
-    
98
-    ## @brief Get the named tuple representing configuration
101
+
102
+    #  @brief Get the named tuple representing configuration
99 103
     @property
100 104
     def settings(self):
101 105
         return self.__confs.lodel2
102
-    
103
-    ## @brief Delete the singleton instance
106
+
107
+    #  @brief Delete the singleton instance
104 108
     @classmethod
105 109
     def stop(cls):
106 110
         del(cls.instance)
@@ -110,7 +114,7 @@ class Settings(object, metaclass=MetaSettings):
110 114
     def started(cls):
111 115
         return cls.instance is not None and cls.instance.__started
112 116
 
113
-    ##@brief An utility method that raises if the singleton is not in a good
117
+    # @brief An utility method that raises if the singleton is not in a good
114 118
     # state
115 119
     #@param expect_instanciated bool : if True we expect that the class is
116 120
     # allready instanciated, else not
@@ -124,17 +128,17 @@ class Settings(object, metaclass=MetaSettings):
124 128
             if cls.started():
125 129
                 raise RuntimeError("The Settings class is already started")
126 130
 
127
-    ##@brief Saves a new configuration for section confname
131
+    # @brief Saves a new configuration for section confname
128 132
     #@param confname is the name of the modified section
129 133
     #@param confvalue is a dict with variables to save
130 134
     #@param validator is a dict with adapted validator
131 135
     @classmethod
132
-    def set(cls, confname, confvalue,validator):
136
+    def set(cls, confname, confvalue, validator):
133 137
         loader = SettingsLoader(cls.instance.__conf_dir)
134
-        confkey=confname.rpartition('.')
138
+        confkey = confname.rpartition('.')
135 139
         loader.setoption(confkey[0], confkey[2], confvalue, validator)
136 140
 
137
-    ##@brief This method handles Settings instance bootstraping
141
+    # @brief This method handles Settings instance bootstraping
138 142
     def __bootstrap(self):
139 143
         LodelContext.expose_modules(globals(), {
140 144
             'lodel.plugin.plugins': ['Plugin', 'PluginError']})
@@ -144,9 +148,9 @@ class Settings(object, metaclass=MetaSettings):
144 148
         else:
145 149
             lodel2_specs = self.__conf_specs
146 150
             self.__conf_specs = None
147
-        loader = SettingsLoader(self.__conf_dir) 
151
+        loader = SettingsLoader(self.__conf_dir)
148 152
         plugin_list = []
149
-        for ptype_name,ptype in Plugin.plugin_types().items():
153
+        for ptype_name, ptype in Plugin.plugin_types().items():
150 154
             pls = ptype.plist_confspecs()
151 155
             lodel2_specs = confspec_append(lodel2_specs, **pls)
152 156
             cur_list = loader.getoption(
@@ -162,13 +166,15 @@ class Settings(object, metaclass=MetaSettings):
162 166
                 plugin_list += cur_list
163 167
             except TypeError:
164 168
                 plugin_list += [cur_list]
165
-        #Checking confspecs
169
+        # Checking confspecs
166 170
         for section in lodel2_specs:
167 171
             if section.lower() != section:
168
-                raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
172
+                raise SettingsError(
173
+                    "Only lower case are allowed in section name (thank's ConfigParser...)")
169 174
             for kname in lodel2_specs[section]:
170 175
                 if kname.lower() != kname:
171
-                    raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
176
+                    raise SettingsError(
177
+                        "Only lower case are allowed in section name (thank's ConfigParser...)")
172 178
 
173 179
         # Starting the Plugins class
174 180
         logger.debug("Starting lodel.plugin.Plugin class")
@@ -181,13 +187,13 @@ class Settings(object, metaclass=MetaSettings):
181 187
                 specs.append(Plugin.get(plugin_name).confspecs)
182 188
             except PluginError as e:
183 189
                 errors.append(SettingsError(msg=str(e)))
184
-        if len(errors) > 0: #Raise all plugins import errors
190
+        if len(errors) > 0:  # Raise all plugins import errors
185 191
             raise SettingsErrors(errors)
186 192
         self.__conf_specs = self.__merge_specs(specs)
187 193
         self.__populate_from_specs(self.__conf_specs, loader)
188 194
         self.__started = True
189
-    
190
-    ##@brief Produce a configuration specification dict by merging all specifications
195
+
196
+    # @brief Produce a configuration specification dict by merging all specifications
191 197
     #
192 198
     # Merges global lodel2 conf spec from @ref lodel.settings.validator.LODEL2_CONF_SPECS
193 199
     # and configuration specifications from loaded plugins
@@ -198,31 +204,35 @@ class Settings(object, metaclass=MetaSettings):
198 204
         for spec in specs:
199 205
             for section in spec:
200 206
                 if section.lower() != section:
201
-                    raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
207
+                    raise SettingsError(
208
+                        "Only lower case are allowed in section name (thank's ConfigParser...)")
202 209
                 if section not in res:
203 210
                     res[section] = dict()
204 211
                 for kname in spec[section]:
205 212
                     if kname.lower() != kname:
206
-                        raise SettingsError("Only lower case are allowed in section name (thank's ConfigParser...)")
213
+                        raise SettingsError(
214
+                            "Only lower case are allowed in section name (thank's ConfigParser...)")
207 215
                     if kname in res[section]:
208
-                        raise SettingsError("Duplicated key '%s' in section '%s'" % (kname, section))
216
+                        raise SettingsError("Duplicated key '%s' in section '%s'" %
217
+                                            (kname, section))
209 218
                     res[section.lower()][kname] = copy.copy(spec[section][kname])
210 219
         return res
211
-    
212
-    ##@brief Populate the Settings instance with options values fetched with the loader from merged specs
220
+
221
+    # @brief Populate the Settings instance with options values fetched with the loader from merged specs
213 222
     #
214 223
     # Populate the __confs attribute
215 224
     # @param specs dict : Settings specification dictionnary as returned by __merge_specs
216 225
     # @param loader SettingsLoader : A SettingsLoader instance
217 226
     def __populate_from_specs(self, specs, loader):
218 227
         self.__confs = dict()
219
-        specs = copy.copy(specs) #Avoid destroying original specs dict (may be useless)
228
+        specs = copy.copy(specs)  # Avoid destroying original specs dict (may be useless)
220 229
         # Construct final specs dict replacing variable sections
221 230
         # by the actual existing sections
222
-        variable_sections = [ section for section in specs if section.endswith('.*') ]
231
+        variable_sections = [section for section in specs if section.endswith('.*')]
223 232
         for vsec in variable_sections:
224 233
             preffix = vsec[:-2]
225
-            for section in loader.getsection(preffix, 'default'): #WARNING : hardcoded default section
234
+            # WARNING : hardcoded default section
235
+            for section in loader.getsection(preffix, 'default'):
226 236
                 specs[section] = copy.copy(specs[vsec])
227 237
             del(specs[vsec])
228 238
         # Fetching values for sections
@@ -238,8 +248,8 @@ class Settings(object, metaclass=MetaSettings):
238 248
 
239 249
         self.__confs_to_namedtuple()
240 250
         pass
241
-    
242
-    ##@brief Transform the __confs attribute into imbricated namedtuple
251
+
252
+    # @brief Transform the __confs attribute into imbricated namedtuple
243 253
     #
244 254
     # For example an option named "foo" in a section named "hello.world" will
245 255
     # be acessible with self.__confs.hello.world.foo
@@ -257,7 +267,7 @@ class Settings(object, metaclass=MetaSettings):
257 267
             section_name = ""
258 268
             cur = section_tree
259 269
             for sec_part in spl:
260
-                section_name += sec_part+'.'
270
+                section_name += sec_part + '.'
261 271
                 if sec_part not in cur:
262 272
                     cur[sec_part] = dict()
263 273
                 cur = cur[sec_part]
@@ -267,35 +277,35 @@ class Settings(object, metaclass=MetaSettings):
267 277
                     raise SettingsError("Duplicated key for '%s.%s'" % (section_name, kname))
268 278
                 cur[kname] = kval
269 279
 
270
-        path = [ ('root', section_tree) ]
280
+        path = [('root', section_tree)]
271 281
         visited = set()
272
-        
282
+
273 283
         curname = 'root'
274 284
         nodename = 'Lodel2Settings'
275 285
         cur = section_tree
276 286
         while True:
277 287
             visited.add(nodename)
278
-            left = [    (kname, cur[kname])
279
-                        for kname in cur
280
-                        if nodename+'.'+kname.title() not in visited and isinstance(cur[kname], dict)
288
+            left = [(kname, cur[kname])
289
+                    for kname in cur
290
+                    if nodename + '.' + kname.title() not in visited and isinstance(cur[kname], dict)
281 291
                     ]
282 292
             if len(left) == 0:
283 293
                 name, leaf = path.pop()
284 294
                 typename = nodename.replace('.', '')
285 295
                 if len(path) == 0:
286 296
                     # END
287
-                    self.__confs = self.__tree2namedtuple(leaf,typename)
297
+                    self.__confs = self.__tree2namedtuple(leaf, typename)
288 298
                     break
289 299
                 else:
290
-                    path[-1][1][name] = self.__tree2namedtuple(leaf,typename)
300
+                    path[-1][1][name] = self.__tree2namedtuple(leaf, typename)
291 301
                 nodename = '.'.join(nodename.split('.')[:-1])
292 302
                 cur = path[-1][1]
293 303
             else:
294 304
                 curname, cur = left[0]
295
-                path.append( (curname, cur) )
305
+                path.append((curname, cur))
296 306
                 nodename += '.' + curname.title()
297
-    
298
-    ##@brief Forge a named tuple given a conftree node
307
+
308
+    # @brief Forge a named tuple given a conftree node
299 309
     # @param conftree dict : A conftree node
300 310
     # @param name str
301 311
     # @return a named tuple with fieldnames corresponding to conftree keys
@@ -303,11 +313,13 @@ class Settings(object, metaclass=MetaSettings):
303 313
         ResNamedTuple = namedtuple(name, conftree.keys())
304 314
         return ResNamedTuple(**conftree)
305 315
 
316
+
306 317
 class MetaSettingsRO(type):
318
+
307 319
     def __getattr__(self, name):
308 320
         return getattr(Settings.s, name)
309
-        
310 321
 
311
-## @brief A class that provide . notation read only access to configurations
322
+
323
+#  @brief A class that provide . notation read only access to configurations
312 324
 class SettingsRO(object, metaclass=MetaSettingsRO):
313 325
     pass

+ 52
- 60
lodel/settings/settings_loader.py View File

@@ -8,28 +8,27 @@ from lodel.context import LodelContext
8 8
 
9 9
 LodelContext.expose_modules(globals(), {
10 10
     'lodel.logger': 'logger',
11
-    'lodel.settings.utils': ['SettingsError', 'SettingsErrors'],
12
-    'lodel.settings.validator': ['SettingsValidationError']})
11
+    'lodel.settings.utils': ['SettingsError', 'SettingsErrors']})
13 12
 
14 13
 ##@brief Merges and loads configuration files
15 14
 class SettingsLoader(object):
16
-    
15
+
17 16
     ## To avoid the DEFAULT section whose values are found in all sections, we
18 17
     # have to give it an unsual name
19 18
     DEFAULT_SECTION = 'lodel2_default_passaway_tip'
20
-    
19
+
21 20
     ## @brief Virtual filename when default value is used
22 21
     DEFAULT_FILENAME = 'default_value'
23 22
 
24 23
     ##@brief Constructor
25 24
     # @param conf_path str : conf.d path
26
-    def __init__(self,conf_path):
27
-        self.__conf_path=conf_path
28
-        self.__conf_sv=dict()
29
-        self.__conf=self.__merge()
25
+    def __init__(self, conf_path):
26
+        self.__conf_path = conf_path
27
+        self.__conf_sv = dict()
28
+        self.__conf = self.__merge()
30 29
         # Stores errors
31 30
         self.__errors_list = []
32
-    
31
+
33 32
     ##@brief Lists and merges files in settings_loader.conf_path
34 33
     # @return dict()
35 34
     def __merge(self):
@@ -38,26 +37,24 @@ class SettingsLoader(object):
38 37
         logger.debug("SettingsLoader found those settings files : %s" % (
39 38
             ', '.join(l_dir)))
40 39
 
41
-        for f_ini in l_dir:  
42
-            config = configparser.ConfigParser(default_section = self.DEFAULT_SECTION ,interpolation=None)
40
+        for f_ini in l_dir:
41
+            config = configparser.ConfigParser(default_section=self.DEFAULT_SECTION, interpolation=None)
43 42
             config.read(f_ini)
44
-            for section in [ s for s in config if s != self.DEFAULT_SECTION ]:
43
+            for section in [s for s in config if s != self.DEFAULT_SECTION]:
45 44
                 if section not in conf:
46 45
                     conf[section] = dict()
47 46
                 for param in config[section]:
48
-                    if param not in conf[section]: 
49
-                        conf[section][param]=dict()
47
+                    if param not in conf[section]:
48
+                        conf[section][param] = dict()
50 49
                         conf[section][param]['value'] = config[section][param]
51 50
                         conf[section][param]['file'] = f_ini
52
-                        self.__conf_sv[section + ':' + param]=f_ini
51
+                        self.__conf_sv[section + ':' + param] = f_ini
53 52
                     else:
54
-                        raise SettingsError("Error redeclaration of key %s in section %s. Found in %s and %s" % (
55
-            section,
56
-            param,
57
-            f_ini,
58
-            conf[section][param]['file']))
53
+                        raise SettingsError("Error redeclaration of key %s \
54
+                            in section %s. Found in %s and %s" % (\
55
+                            section, param, f_ini, conf[section][param]['file']))
59 56
         return conf
60
-    
57
+
61 58
     ##@brief Returns option if exists default_value else and validates
62 59
     # @param section str : name of the section
63 60
     # @param keyname str
@@ -65,8 +62,8 @@ class SettingsLoader(object):
65 62
     # @param default_value *
66 63
     # @param mandatory bool
67 64
     # @return the option
68
-    def getoption(self,section,keyname,validator,default_value=None,mandatory=False):
69
-        conf=self.__conf
65
+    def getoption(self, section, keyname, validator, default_value=None, mandatory=False):
66
+        conf = self.__conf
70 67
         if section not in conf:
71 68
             conf[section] = dict()
72 69
 
@@ -85,35 +82,31 @@ class SettingsLoader(object):
85 82
         if result is None:
86 83
             if default_value is None and mandatory:
87 84
                 msg = "Default value mandatory for option %s" % keyname
88
-                expt = SettingsError(   msg = msg,
89
-                                        key_id = section+'.'+keyname,
90
-                                        filename = sec[keyname]['file'])
85
+                expt = SettingsError(msg=msg, key_id=section+'.'+keyname, \
86
+                    filename=sec[keyname]['file'])
91 87
                 self.__errors_list.append(expt)
92 88
                 return
93 89
             else:
94
-                sec[keyname]=dict()
90
+                sec[keyname] = dict()
95 91
                 sec[keyname]['value'] = default_value
96 92
                 sec[keyname]['file'] = SettingsLoader.DEFAULT_FILENAME
97 93
                 result = default_value
98
-                logger.debug("Using default value for configuration key %s:%s" % (
99
-                    section, keyname))
94
+                logger.debug("Using default value for configuration key %s:%s" \
95
+                    % (section, keyname))
100 96
 
101 97
         try:
102 98
             return validator(result)
103 99
         except Exception as e:
104 100
             # Generating nice exceptions
105 101
             if False and sec[keyname]['file'] == SettingsLoader.DEFAULT_FILENAME:
106
-                expt = SettingsError(   msg = 'Mandatory settings not found',
107
-                                        key_id = section+'.'+keyname)
102
+                expt = SettingsError(msg='Mandatory settings not found', \
103
+                                    key_id=section+'.'+keyname)
108 104
                 self.__errors_list.append(expt)
109 105
             else:
110
-                expt = SettingsValidationError(
111
-                                                "For %s.%s : %s" % 
112
-                                                (section, keyname,e)
113
-                )
114
-                expt2 = SettingsError(  msg = str(expt),
115
-                                        key_id = section+'.'+keyname,
116
-                                        filename = sec[keyname]['file'])
106
+                #expt = ValidationError("For %s.%s : %s" % (section, keyname, e))
107
+                expt2 = SettingsError(msg=str(expt), \
108
+                                        key_id=section+'.'+keyname, \
109
+                                        filename=sec[keyname]['file'])
117 110
                 self.__errors_list.append(expt2)
118 111
             return
119 112
 
@@ -123,38 +116,38 @@ class SettingsLoader(object):
123 116
     # @param value str
124 117
     # @param validator callable : takes one argument value and raises validation fail
125 118
     # @return the option
126
-    def setoption(self,section,keyname,value,validator):
127
-        f_conf=copy.copy(self.__conf[section][keyname]['file'])
119
+    def setoption(self, section, keyname, value, validator):
120
+        f_conf = copy.copy(self.__conf[section][keyname]['file'])
128 121
         if f_conf == SettingsLoader.DEFAULT_FILENAME:
129 122
             f_conf = self.__conf_path + '/generated.ini'
130 123
 
131
-        conf=self.__conf
124
+        conf = self.__conf
132 125
         conf[section][keyname] = value
133 126
         config = configparser.ConfigParser()
134 127
         config.read(f_conf)
135 128
         if section not in config:
136
-            config[section]={}
129
+            config[section] = {}
137 130
         config[section][keyname] = validator(value)
138
-        
131
+
139 132
         with open(f_conf, 'w') as configfile:
140 133
             config.write(configfile)
141
-            
134
+
142 135
     ##@brief Saves new partial configuration. Writes in the conf files corresponding
143 136
     # @param sections dict
144 137
     # @param validators dict of callable : takes one argument value and raises validation fail
145 138
     def saveconf(self, sections, validators):
146 139
         for sec in sections:
147 140
             for kname in sections[sec]:
148
-                self.setoption(sec,kname,sections[sec][kname],validators[sec][kname])
149
-    
141
+                self.setoption(sec, kname, sections[sec][kname], validators[sec][kname])
142
+
150 143
     ##@brief Returns the section to be configured
151 144
     # @param section_prefix str
152 145
     # @param default_section str
153 146
     # @return the section as dict()
154
-    def getsection(self,section_prefix,default_section=None):
155
-        conf=copy.copy(self.__conf)
156
-       
157
-        sections=[]
147
+    def getsection(self, section_prefix, default_section=None):
148
+        conf = copy.copy(self.__conf)
149
+
150
+        sections = []
158 151
         if section_prefix in conf:
159 152
             sections.append(section_prefix)
160 153
         for sect_names in conf:
@@ -162,34 +155,33 @@ class SettingsLoader(object):
162 155
                 pass
163 156
             elif sect_names.startswith(section_prefix + '.'):
164 157
                 sections.append(sect_names)
165
-        if sections == [] and default_section: 
166
-             sections.append(section_prefix + '.' + default_section)
158
+        if sections == [] and default_section:
159
+            sections.append(section_prefix + '.' + default_section)
167 160
         elif sections == []:
168 161
             raise NameError("Not existing settings section : %s" % section_prefix)
169
-            
162
+
170 163
         return sections
171
-    
164
+
172 165
     ##@brief Returns invalid settings
173 166
     #
174
-    # This method returns all the settings that was not fecthed by 
167
+    # This method returns all the settings that was not fecthed by
175 168
     # getsection() method. For the Settings object it allows to know
176 169
     # the list of invalids settings keys
177 170
     # @return a dict with SECTION_NAME+":"+KEY_NAME as key and the filename
178 171
     # where the settings was found as value
179 172
     def getremains(self):
180 173
         return self.__conf_sv
181
-    
174
+
182 175
     ##@brief Raise a SettingsErrors exception if some confs remains
183 176
     #@note typically used at the end of Settings bootstrap
184 177
     def raise_errors(self):
185 178
         remains = self.getremains()
186 179
         err_l = self.__errors_list
187 180
         for key_id, filename in remains.items():
188
-            err_l.append(SettingsError( msg = "Invalid configuration key",
189
-                                        key_id = key_id,
190
-                                        filename = filename))
181
+            err_l.append(SettingsError(msg="Invalid configuration key", \
182
+                                    key_id=key_id, \
183
+                                    filename =filename))
191 184
         if len(err_l) > 0:
192 185
             raise SettingsErrors(err_l)
193 186
         else:
194 187
             return
195
-

+ 1
- 1
lodel/utils/datetime.py View File

@@ -6,4 +6,4 @@ def get_utc_timestamp():
6 6
     d = datetime.datetime.utcnow()
7 7
     epoch = datetime.datetime(1970, 1, 1)
8 8
     t = (d - epoch).total_seconds()
9
-    return t
9
+    return t

+ 17
- 16
lodel/utils/mlstring.py View File

@@ -5,9 +5,9 @@ import hashlib
5 5
 import json
6 6
 
7 7
 
8
-##@brief Stores multilangage string
8
+# @brief Stores multilangage string
9 9
 class MlString(object):
10
-    
10
+
11 11
     __default_lang = 'eng'
12 12
 
13 13
     langs = [
@@ -17,7 +17,7 @@ class MlString(object):
17 17
         'esp',
18 18
     ]
19 19
 
20
-    ##@brief Create a new MlString instance
20
+    # @brief Create a new MlString instance
21 21
     # @param arg str | dict : Can be a json string, a string or a dict. It could be also a MlString object
22 22
     def __init__(self, arg):
23 23
         self.values = dict()
@@ -31,11 +31,12 @@ class MlString(object):
31 31
         elif isinstance(arg, MlString):
32 32
             self.values = copy.copy(arg.values)
33 33
         else:
34
-            raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
35
-    
36
-    ##@brief Return a translation given a lang
34
+            raise ValueError(
35
+                '<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
36
+
37
+    # @brief Return a translation given a lang
37 38
     # @param lang str | None : If None return default lang translation
38
-    def get(self, lang = None):
39
+    def get(self, lang=None):
39 40
         lang = self.__default_lang if lang is None else lang
40 41
         if not self.lang_is_valid(lang):
41 42
             raise ValueError("Invalid lang : '%s'" % lang)
@@ -44,7 +45,7 @@ class MlString(object):
44 45
         else:
45 46
             return str(self)
46 47
 
47
-    ##@brief Set a translation
48
+    # @brief Set a translation
48 49
     # @param lang str : the lang
49 50
     # @param val str | None:  the translation if None delete the translation
50 51
     def set(self, lang, val):
@@ -57,7 +58,7 @@ class MlString(object):
57 58
         else:
58 59
             self.values[lang] = val
59 60
 
60
-    ##@brief Checks that given lang is valid
61
+    # @brief Checks that given lang is valid
61 62
     # @param lang str : the lang
62 63
     @classmethod
63 64
     def lang_is_valid(cls, lang):
@@ -65,16 +66,16 @@ class MlString(object):
65 66
             raise ValueError('Invalid value for lang. Str expected but %s found' % type(lang))
66 67
         return lang in cls.langs
67 68
 
68
-    ##@brief Get or set the default lang
69
+    # @brief Get or set the default lang
69 70
     @classmethod
70
-    def default_lang(cls, lang = None):
71
+    def default_lang(cls, lang=None):
71 72
         if lang is None:
72 73
             return cls.__default_lang
73 74
         if not cls.lang_is_valid(lang):
74 75
             raise ValueError('lang "%s" is not valid"' % lang)
75 76
         cls.__default_lang = lang
76
-    
77
-    ##@brief Return a mlstring loaded from a json string
77
+
78
+    # @brief Return a mlstring loaded from a json string
78 79
     # @param json_str str : Json string
79 80
     @classmethod
80 81
     def from_json(cls, json_str):
@@ -89,13 +90,13 @@ class MlString(object):
89 90
     def d_hash(self):
90 91
         m = hashlib.md5()
91 92
         for lang in sorted(list(self.values.keys())):
92
-            m.update(bytes(lang+";"+self.values[lang], 'utf-8'))
93
+            m.update(bytes(lang + ";" + self.values[lang], 'utf-8'))
93 94
         return int.from_bytes(m.digest(), byteorder='big')
94 95
 
95 96
     def __eq__(self, a):
96 97
         return hash(self) == hash(a)
97
-    
98
-    ## @return The default langage translation or any available translation
98
+
99
+    # @return The default langage translation or any available translation
99 100
     def __str__(self):
100 101
         if self.__default_lang in self.values:
101 102
             return self.values[self.__default_lang]

+ 2
- 0
lodel/validator/Makefile.am View File

@@ -0,0 +1,2 @@
1
+validator_PYTHON=*.py
2
+validatordir=$(pkgpythondir)/validator

+ 6
- 0
lodel/validator/__init__.py View File

@@ -0,0 +1,6 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+## @package lodel.validator Lodel2 validator package
4
+#
5
+
6
+

lodel/settings/validator.py → lodel/validator/validator.py View File

@@ -9,55 +9,63 @@ import copy
9 9
 
10 10
 from lodel.context import LodelContext
11 11
 LodelContext.expose_modules(globals(), {
12
+    'lodel.mlnamedobject.mlnamedobject': ['MlNamedObject'],
12 13
     'lodel.exceptions': ['LodelException', 'LodelExceptions',
13
-        'LodelFatalError', 'FieldValidationError']})
14
+                         'LodelFatalError', 'FieldValidationError']})
14 15
 
15
-## @package lodel.settings.validator Lodel2 settings validators/cast module
16
+# @package lodel.settings.validator Lodel2 settings validators/cast module
16 17
 #
17
-# Validator are registered in the SettingValidator class.
18
+# Validator are registered in the Validator class.
18 19
 # @note to get a list of registered default validators just run
19 20
 # <pre>$ python scripts/settings_validator.py</pre>
20 21
 
21
-##@brief Exception class that should be raised when a validation fails
22
-class SettingsValidationError(Exception):
22
+# @brief Exception class that should be raised when a validation fails
23
+
24
+
25
+class ValidationError(Exception):
23 26
     pass
24 27
 
25
-##@brief Handles settings validators
28
+# @brief Handles settings validators
26 29
 #
27 30
 # Class instance are callable objects that takes a value argument (the value to validate). It raises
28
-# a SettingsValidationError if validation fails, else it returns a properly
31
+# a ValidationError if validation fails, else it returns a properly
29 32
 # casted value.
30 33
 #@todo implement an IP validator and use it in multisite confspec
31
-class SettingValidator(object):
32
-    
34
+
35
+
36
+class Validator(MlNamedObject):
37
+
33 38
     _validators = dict()
34 39
     _description = dict()
35
-    
36
-    ##@brief Instanciate a validator
40
+
41
+    # @brief Instanciate a validator
37 42
     #@param name str : validator name
38 43
     #@param none_is_valid bool : if True None will be validated
39 44
     #@param **kwargs : more arguement for the validator
40
-    def __init__(self, name, none_is_valid = False, **kwargs):
45
+    def __init__(self, name, none_is_valid=False, display_name=None, help_text=None, **kwargs):
41 46
         if name is not None and name not in self._validators:
42 47
             raise LodelFatalError("No validator named '%s'" % name)
43 48
         self.__none_is_valid = none_is_valid
44 49
         self.__name = name
45 50
         self._opt_args = kwargs
51
+        if display_name is None:
52
+            display_name = name
53
+        super().__init__(display_name, help_text)
46 54
 
47
-    ##@brief Call the validator
55
+    # @brief Call the validator
48 56
     # @param value *
49 57
     # @return properly casted value
50
-    # @throw SettingsValidationError
58
+    # @throw ValidationError
51 59
     def __call__(self, value):
52 60
         if self.__none_is_valid and value is None:
53 61
             return None
54 62
         try:
55 63
             ret = self._validators[self.__name](value, **self._opt_args)
56 64
             return ret
57
-        except Exception as e:
58
-            raise SettingsValidationError(e)
59
-    
60
-    ##@brief Register a new validator
65
+        except Exception as exp:
66
+            raise ValidationError(exp)
67
+
68
+    # @brief Register a new validator
61 69
     # @param name str : validator name
62 70
     # @param callback callable : the function that will validate a value
63 71
     # @param description str
@@ -70,76 +78,68 @@ class SettingValidator(object):
70 78
             raise TypeError("Callable expected but got %s" % type(callback))
71 79
         cls._validators[name] = callback
72 80
         cls._description[name] = description
73
-    
74
-    ##@brief Get the validator list associated with description
81
+
82
+    # @brief Get the validator list associated with description
75 83
     @classmethod
76 84
     def validators_list(cls):
77 85
         return copy.copy(cls._description)
78 86
 
79
-    ##@brief Create and register a list validator
87
+    # @brief Create and register a list validator
80 88
     # @param elt_validator callable : The validator that will be used for validate each elt value
81 89
     # @param validator_name str
82 90
     # @param description None | str
83 91
     # @param separator str : The element separator
84
-    # @return A SettingValidator instance
92
+    # @return A Validator instance
85 93
     @classmethod
86
-    def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
94
+    def create_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
87 95
         def list_validator(value):
88 96
             res = list()
89
-            errors = list()
90 97
             for elt in value.split(separator):
91 98
                 elt = elt_validator(elt)
92 99
                 if len(elt) > 0:
93 100
                     res.append(elt)
94 101
             return res
95 102
         description = "Convert value to an array" if description is None else description
96
-        cls.register_validator(
97
-                                validator_name,
98
-                                list_validator,
99
-                                description)
103
+        cls.register_validator(validator_name, list_validator, description)
100 104
         return cls(validator_name)
101
- 
102
-    ##@brief Create and register a list validator which reads an array and returns a string
105
+
106
+    # @brief Create and register a list validator which reads an array and returns a string
103 107
     # @param elt_validator callable : The validator that will be used for validate each elt value
104 108
     # @param validator_name str
105 109
     # @param description None | str
106 110
     # @param separator str : The element separator
107
-    # @return A SettingValidator instance
111
+    # @return A Validator instance
108 112
     @classmethod
109
-    def create_write_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
113
+    def create_write_list_validator(cls, validator_name, elt_validator, description=None, separator=','):
110 114
         def write_list_validator(value):
111 115
             res = ''
112
-            errors = list()
113 116
             for elt in value:
114 117
                 res += elt_validator(elt) + ','
115
-            return res[:len(res)-1]
118
+            return res[:len(res) - 1]
116 119
         description = "Convert value to a string" if description is None else description
117
-        cls.register_validator(
118
-                                validator_name,
119
-                                write_list_validator,
120
-                                description)
120
+        cls.register_validator(validator_name, write_list_validator, description)
121 121
         return cls(validator_name)
122
-    
123
-    ##@brief Create and register a regular expression validator
122
+
123
+    # @brief Create and register a regular expression validator
124 124
     # @param pattern str : regex pattern
125 125
     # @param validator_name str : The validator name
126 126
     # @param description str : Validator description
127
-    # @return a SettingValidator instance
127
+    # @return a Validator instance
128 128
     @classmethod
129
-    def create_re_validator(cls, pattern, validator_name, description = None):
129
+    def create_re_validator(cls, pattern, validator_name, description=None):
130 130
         def re_validator(value):
131 131
             if not re.match(pattern, value):
132
-                raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
132
+                raise ValidationError(
133
+                    "The value '%s' doesn't match the following pattern '%s'"
134
+                    % pattern)
133 135
             return value
134
-        #registering the validator
135
-        cls.register_validator(
136
-                                validator_name,
137
-                                re_validator,
138
-                                ("Match value to '%s'" % pattern) if description is None else description)
136
+        # registering the validator
137
+        cls.register_validator(validator_name, re_validator,
138
+                               ("Match value to '%s'" % pattern)
139
+                               if description is None else description)
139 140
         return cls(validator_name)
140 141
 
141
-    
142
-    ## @return a list of registered validators
142
+    #  @return a list of registered validators
143 143
     @classmethod
144 144
     def validators_list_str(cls):
145 145
         result = ''
@@ -150,20 +150,26 @@ class SettingValidator(object):
150 150
             result += "\n"
151 151
         return result
152 152
 
153
-##@brief Integer value validator callback
153
+# @brief Integer value validator callback
154
+
155
+
154 156
 def int_val(value):
155 157
     return int(value)
156 158
 
157
-##@brief Output file validator callback
159
+# @brief Output file validator callback
158 160
 # @return A file object (if filename is '-' return sys.stderr)
161
+
162
+
159 163
 def file_err_output(value):
160 164
     if not isinstance(value, str):
161
-        raise SettingsValidationError("A string was expected but got '%s' " % value)
165
+        raise ValidationError("A string was expected but got '%s' " % value)
162 166
     if value == '-':
163 167
         return None
164 168
     return value
165 169
 
166
-##@brief Boolean value validator callback
170
+# @brief Boolean value validator callback
171
+
172
+
167 173
 def boolean_val(value):
168 174
     if isinstance(value, bool):
169 175
         return value
@@ -172,52 +178,66 @@ def boolean_val(value):
172 178
     elif value.strip().lower() == 'false' or value.strip() == '0':
173 179
         value = False
174 180
     else:
175
-        raise SettingsValidationError("A boolean was expected but got '%s' " % value)
181
+        raise ValidationError("A boolean was expected but got '%s' " % value)
176 182
     return bool(value)
177 183
 
178
-##@brief Validate a directory path
184
+# @brief Validate a directory path
185
+
186
+
179 187
 def directory_val(value):
180
-    res = SettingValidator('strip')(value)
188
+    res = Validator('strip')(value)
181 189
     if not os.path.isdir(res):
182
-        raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
190
+        raise ValidationError("Following path don't exists or is not a directory : '%s'" % res)
183 191
     return res
184 192
 
185
-##@brief Validate a loglevel value
193
+# @brief Validate a loglevel value
194
+
195
+
186 196
 def loglevel_val(value):
187 197
     valids = ['DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL']
188 198
     if value.upper() not in valids:
189
-        raise SettingsValidationError(
190
-                "The value '%s' is not a valid loglevel" % value)
199
+        raise ValidationError(
200
+            "The value '%s' is not a valid loglevel" % value)
191 201
     return value.upper()
192 202
 
193
-##@brief Validate a path
203
+# @brief Validate a path
204
+
205
+
194 206
 def path_val(value):
195 207
     if value is None or not os.path.exists(value):
196
-        raise SettingsValidationError(
197
-                "path '%s' doesn't exists" % value)
208
+        raise ValidationError(
209
+            "path '%s' doesn't exists" % value)
198 210
     return value
199 211
 
200
-##@brief Validate None
212
+# @brief Validate None
213
+
214
+
201 215
 def none_val(value):
202 216
     if value is None:
203 217
         return None
204
-    raise SettingsValidationError("This settings cannot be set in configuration file")
218
+    raise ValidationError("This settings cannot be set in configuration file")
219
+
220
+# @brief Validate a string
221
+
205 222
 
206
-##@brief Validate a string
207 223
 def str_val(value):
208 224
     try:
209 225
         return str(value)
210
-    except Exception as e:
211
-        raise SettingsValidationError("Not able to convert value to string : " + str(e))
226
+    except Exception as exp:
227
+        raise ValidationError("Can't to convert value to string: " + str(exp))
228
+
229
+# @brief Validate using a regex
230
+
212 231
 
213
-##@brief Validate using a regex
214 232
 def regex_val(value, pattern):
215 233
     if re.match(pattern, value) is None:
216
-        raise SettingsValidationError("The value '%s' is not validated by : \
217
-r\"%s\"" %(value, pattern))
234
+        raise ValidationError("The value '%s' is not validated by : \
235
+r\"%s\"" % (value, pattern))
218 236
     return value
219 237
 
220
-##@brief Validate a hostname (ipv4 or ipv6)
238
+# @brief Validate a hostname (ipv4 or ipv6)
239
+
240
+
221 241
 def host_val(value):
222 242
     if value == 'localhost':
223 243
         return value
@@ -225,37 +245,103 @@ def host_val(value):
225 245
     try:
226 246
         socket.inet_aton(value)
227 247
         return value
228
-    except (TypeError,OSError):
248
+    except (TypeError, OSError):
229 249
         pass
230 250
     try:
231 251
         socket.inet_pton(socket.AF_INET6, value)
232 252
         return value
233
-    except (TypeError,OSError):
253
+    except (TypeError, OSError):
234 254
         pass
235 255
     try:
236 256
         socket.getaddrinfo(value, 80)
237 257
         return value
238
-    except (TypeError,socket.gaierror):
258
+    except (TypeError, socket.gaierror):
239 259
         msg = "The value '%s' is not a valid host"
240
-        raise SettingsValidationError(msg % value)
260
+        raise ValidationError(msg % value)
261
+
262
+
263
+def custom_list_validator(value, validator_name, validator_kwargs=None):
264
+    validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
265
+    validator = Validator(validator_name, **validator_kwargs)
266
+    for item in value.split():
267
+        validator(item)
268
+    return value.split()
241 269
 
242
-##@brief Validator for Editorial model component
270
+#
271
+#   Default validators registration
272
+#
273
+
274
+Validator.register_validator('custom_list', custom_list_validator,
275
+                             'A list validator that takes a "validator_name" as argument')
276
+
277
+Validator.register_validator('dummy', lambda value: value, 'Validate anything')
278
+
279
+Validator.register_validator('none', none_val, 'Validate None')
280
+
281
+Validator.register_validator('string', str_val, 'Validate string values')
282
+
283
+Validator.register_validator('strip', str.strip, 'String trim')
284
+
285
+Validator.register_validator('int', int_val, 'Integer value validator')
286
+
287
+Validator.register_validator('bool', boolean_val, 'Boolean value validator')
288
+
289
+Validator.register_validator('errfile', file_err_output,
290
+                             'Error output file validator (return stderr if filename is "-")')
291
+
292
+Validator.register_validator('directory', directory_val,
293
+                             'Directory path validator')
294
+
295
+Validator.register_validator('loglevel', loglevel_val, 'Loglevel validator')
296
+
297
+Validator.register_validator('path', path_val, 'path validator')
298
+
299
+Validator.register_validator('host', host_val, 'host validator')
300
+
301
+Validator.register_validator('regex', regex_val,
302
+                             'RegEx name validator (take re as argument)')
303
+
304
+Validator.create_list_validator('list', Validator('strip'), description="Simple list validator. Validate a list of values separated by ','",
305
+                                separator=',')
306
+
307
+Validator.create_list_validator(
308
+    'directory_list',
309
+    Validator('directory'),
310
+    description="Validator for a list of directory path separated with ','",
311
+    separator=',')
312
+
313
+Validator.create_write_list_validator(
314
+    'write_list',
315
+    Validator('directory'),
316
+    description="Validator for an array of values \
317
+        which will be set in a string, separated by ','",
318
+    separator=',')
319
+
320
+Validator.create_re_validator(
321
+    r'^https?://[^\./]+.[^\./]+/?.*$',
322
+    'http_url',
323
+    'Url validator')
324
+
325
+# @brief Validator for Editorial model component
243 326
 #
244 327
 # Designed to validate a conf that indicate a class.field in an EM
245 328
 #@todo modified the hardcoded dyncode import (it's a warning)
329
+
330
+
246 331
 def emfield_val(value):
247
-    LodelContext.expose_modules(globals(), {
248
-        'lodel.plugin.hooks': ['LodelHook']})
332
+    LodelContext.expose_modules(globals(),
333
+                                {'lodel.plugin.hooks': ['LodelHook']})
249 334
     spl = value.split('.')
250 335
     if len(spl) != 2:
251 336
         msg = "Expected a value in the form CLASSNAME.FIELDNAME but got : %s"
252 337
         raise SettingsValidationError(msg % value)
253 338
     value = tuple(spl)
254
-    #Late validation hook
339
+    # Late validation hook
340
+
255 341
     @LodelHook('lodel2_dyncode_bootstraped')
256 342
     def emfield_conf_check(hookname, caller, payload):
257
-        import leapi_dyncode as dyncode # <-- dirty & quick
258
-        classnames = { cls.__name__.lower():cls for cls in dyncode.dynclasses}
343
+        import leapi_dyncode as dyncode  # <-- dirty & quick
344
+        classnames = {cls.__name__.lower(): cls for cls in dyncode.dynclasses}
259 345
         if value[0].lower() not in classnames:
260 346
             msg = "Following dynamic class do not exists in current EM : %s"
261 347
             raise SettingsValidationError(msg % value[0])
@@ -265,13 +351,16 @@ def emfield_val(value):
265 351
             raise SettingsValidationError(msg % value)
266 352
     return value
267 353
 
268
-##@brief Validator for plugin name & optionnaly type
354
+# @brief Validator for plugin name & optionnaly type
269 355
 #
270
-#Able to check that the value is a plugin and if it is of a specific type
271
-def plugin_validator(value, ptype = None):
356
+# Able to check that the value is a plugin and if it is of a specific type
357
+
358
+
359
+def plugin_validator(value, ptype=None):
272 360
     LodelContext.expose_modules(globals(), {
273 361
         'lodel.plugin.hooks': ['LodelHook']})
274 362
     value = copy.copy(value)
363
+
275 364
     @LodelHook('lodel2_dyncode_bootstraped')
276 365
     def plugin_type_checker(hookname, caller, payload):
277 366
         LodelContext.expose_modules(globals(), {
@@ -284,133 +373,39 @@ def plugin_validator(value, ptype = None):
284 373
         except PluginError:
285 374
             msg = "No plugin named %s found"
286 375
             msg %= value
287
-            raise SettingsValidationError(msg)
376
+            raise ValidationError(msg)
288 377
         if plugin._type_conf_name.lower() != ptype.lower():
289 378
             msg = "A plugin of type '%s' was expected but found a plugin \
290 379
 named  '%s' that is a '%s' plugin"
291 380
             msg %= (ptype, value, plugin._type_conf_name)
292
-            raise SettingsValidationError(msg)
381
+            raise ValidationError(msg)
293 382
     return value
294 383
 
295
-def custom_list_validator(value, validator_name, validator_kwargs = None):
296
-    validator_kwargs = dict() if validator_kwargs is None else validator_kwargs
297
-    validator = SettingValidator(validator_name, **validator_kwargs)
298
-    for item in value.split():
299
-        validator(item)
300
-    return value.split()
301
-
302
-#
303
-#   Default validators registration
304
-#
305 384
 
306
-SettingValidator.register_validator(
385
+Validator.register_validator(
307 386
     'plugin',
308 387
     plugin_validator,
309 388
     'plugin name & type validator')
310 389
 
311
-SettingValidator.register_validator(
312
-    'custom_list',
313
-    custom_list_validator,
314
-    'A list validator that takes a "validator_name" as argument')
315
-
316
-SettingValidator.register_validator(
317
-    'dummy',
318
-    lambda value:value,
319
-    'Validate anything')
320
-
321
-SettingValidator.register_validator(
322
-    'none',
323
-    none_val,
324
-    'Validate None')
325
-
326
-SettingValidator.register_validator(
327
-    'string',
328
-    str_val,
329
-    'Validate string values')
330
-
331
-SettingValidator.register_validator(
332
-    'strip',
333
-    str.strip,
334
-    'String trim')
335
-
336
-SettingValidator.register_validator(
337
-    'int',
338
-    int_val,
339
-    'Integer value validator')
340
-
341
-SettingValidator.register_validator(
342
-    'bool',
343
-    boolean_val,
344
-    'Boolean value validator')
345
-
346
-SettingValidator.register_validator(
347
-    'errfile',
348
-    file_err_output,
349
-    'Error output file validator (return stderr if filename is "-")')
350
-
351
-SettingValidator.register_validator(
352
-    'directory',
353
-    directory_val,
354
-    'Directory path validator')
355
-
356
-SettingValidator.register_validator(
357
-    'loglevel',
358
-    loglevel_val,
359
-    'Loglevel validator')
360
-
361
-SettingValidator.register_validator(
362
-    'path',
363
-    path_val,
364
-    'path validator')
365
-
366
-SettingValidator.register_validator(
367
-    'host',
368
-    host_val,
369
-    'host validator')
370
-
371
-SettingValidator.register_validator(
390
+Validator.register_validator(
372 391
     'emfield',
373 392
     emfield_val,
374 393
     'EmField name validator')
375 394
 
376
-SettingValidator.register_validator(
377
-    'regex',
378
-    regex_val,
379
-    'RegEx name validator (take re as argument)')
380
-
381
-SettingValidator.create_list_validator(
382
-    'list',
383
-    SettingValidator('strip'),
384
-    description = "Simple list validator. Validate a list of values separated by ','",
385
-    separator = ',')
386
-
387
-SettingValidator.create_list_validator(
388
-    'directory_list',
389
-    SettingValidator('directory'),
390
-    description = "Validator for a list of directory path separated with ','",
391
-    separator = ',')
392
-SettingValidator.create_write_list_validator(
393
-    'write_list',
394
-    SettingValidator('directory'),
395
-    description = "Validator for an array of values which will be set in a string, separated by ','",
396
-    separator = ',')
397
-SettingValidator.create_re_validator(
398
-    r'^https?://[^\./]+.[^\./]+/?.*$',
399
-    'http_url',
400
-    'Url validator')
401
-
402 395
 #
403 396
 #   Lodel 2 configuration specification
404 397
 #
405 398
 
406
-##@brief Append a piece of confspec
399
+# @brief Append a piece of confspec
407 400
 #@note orig is modified during the process
408 401
 #@param orig dict : the confspec to update
409 402
 #@param section str : section name
410 403
 #@param key str
411
-#@param validator SettingValidator : the validator to use to check this configuration key's value
404
+#@param validator Validator : the validator to use to check this configuration key's value
412 405
 #@param default
413 406
 #@return new confspec
407
+
408
+
414 409
 def confspec_append(orig, section, key, validator, default):
415 410
     if section not in orig:
416 411
         orig[section] = dict()
@@ -418,41 +413,33 @@ def confspec_append(orig, section, key, validator, default):
418 413
         orig[section][key] = (default, validator)
419 414
     return orig
420 415
 
421
-##@brief Global specifications for lodel2 settings
416
+# @brief Global specifications for lodel2 settings
422 417
 LODEL2_CONF_SPECS = {
423 418
     'lodel2': {
424
-        'debug': (  True,
425
-                    SettingValidator('bool')),
426
-        'sitename': (   'noname',
427
-                        SettingValidator('strip')),
428
-        'runtest': (    False,
429
-                        SettingValidator('bool')),
419
+        'debug': (True, Validator('bool')),
420
+        'sitename': ('noname', Validator('strip')),
421
+        'runtest': (False, Validator('bool')),
430 422
     },
431
-    'lodel2.logging.*' : {
432
-        'level': (  'ERROR',
433
-                    SettingValidator('loglevel')),
434
-        'context': (    False,
435
-                        SettingValidator('bool')),
436
-        'filename': (   "-",
437
-                        SettingValidator('errfile', none_is_valid = False)),
438
-        'backupcount': (    5,
439
-                            SettingValidator('int', none_is_valid = False)),
440
-        'maxbytes': (   1024*10,
441
-                        SettingValidator('int', none_is_valid = False)),
423
+    'lodel2.logging.*': {
424
+        'level': ('ERROR', Validator('loglevel')),
425
+        'context': (False, Validator('bool')),
426
+        'filename': ("-", Validator('errfile', none_is_valid=False)),
427
+        'backupcount': (5, Validator('int', none_is_valid=False)),
428
+        'maxbytes': (1024 * 10, Validator('int', none_is_valid=False)),
442 429
     },
443 430
     'lodel2.editorialmodel': {
444
-        'emfile': ( 'em.pickle', SettingValidator('strip')),
445
-        'emtranslator': ( 'picklefile', SettingValidator('strip')),
446
-        'dyncode': ( 'leapi_dyncode.py', SettingValidator('strip')),
447
-        'groups': ( '', SettingValidator('list')),
448
-        'editormode': ( False, SettingValidator('bool')),
431
+        'emfile': ('em.pickle', Validator('strip')),
432
+        'emtranslator': ('picklefile', Validator('strip')),
433
+        'dyncode': ('leapi_dyncode.py', Validator('strip')),
434
+        'groups': ('', Validator('list')),
435
+        'editormode': (False, Validator('bool')),
449 436
     },
450 437
     'lodel2.datasources.*': {
451
-        'read_only': (False, SettingValidator('bool')),
452
-        'identifier': ( None, SettingValidator('string')),
438
+        'read_only': (False, Validator('bool')),
439
+        'identifier': (None, Validator('string')),
453 440
     },
454 441
     'lodel2.auth': {
455
-        'login_classfield': ('user.login', SettingValidator('emfield')),
456
-        'pass_classfield': ('user.password', SettingValidator('emfield')),
442
+        'login_classfield': ('user.login', Validator('emfield')),
443
+        'pass_classfield': ('user.password', Validator('emfield')),
457 444
     },
458 445
 }

+ 12
- 10
nocontext_tests.py View File

@@ -1,12 +1,12 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3
-##@brief Loader for tests which do not need an lodel installation
3
+# @brief Loader for tests which do not need an lodel installation
4 4
 #
5 5
 # Options
6 6
 ################
7
-# 
7
+#
8 8
 # @note We can pass the path to a directory to write results file, nocontext_tests.log
9
-# It has to be at first, otherwise it will not be taken 
9
+# It has to be at first, otherwise it will not be taken
10 10
 # and the default one, current directory, will be used.
11 11
 # The results are not displayed, only stored in nocontext_tests.log
12 12
 #
@@ -19,20 +19,22 @@
19 19
 #
20 20
 #
21 21
 
22
-import sys, os, os.path
22
+import sys
23
+import os
24
+import os.path
23 25
 import unittest
24 26
 
25 27
 
26 28
 loader = unittest.TestLoader()
27 29
 
28 30
 if ((len(sys.argv) > 1) and (sys.argv[1].startswith('-')) is False):
29
-	dpath = sys.argv[1]
31
+    dpath = sys.argv[1]
30 32
 else:
31
-	dpath = '.'
33
+    dpath = '.'
32 34
 
33 35
 suite = loader.discover('tests', pattern='nc_test*.py')
34
-with open(dpath+'/nocontext_tests.log', 'w') as logfile:
36
+with open(dpath + '/nocontext_tests.log', 'w') as logfile:
35 37
     unittest.TextTestRunner(
36
-    	logfile,
37
-        failfast = '-f' in sys.argv,
38
-        verbosity = 2 if '-v' in sys.argv else 1).run(suite)
38
+        logfile,
39
+        failfast='-f' in sys.argv,
40
+        verbosity=2 if '-v' in sys.argv else 1).run(suite)

+ 2
- 2
scripts/settings_validator.py View File

@@ -2,5 +2,5 @@
2 2
 import sys
3 3
 import os, os.path
4 4
 sys.path.append(os.path.dirname(os.getcwd()+'/..'))
5
-from lodel.settings.validator import SettingValidator
6
-print(SettingValidator.validators_list_str())
5
+from lodel.validator.validator import Validator
6
+print(Validator.validators_list_str())

+ 14
- 14
tests/settings/test_validator.py View File

@@ -5,52 +5,52 @@ from unittest import mock
5 5
 from unittest.mock import patch
6 6
 
7 7
 from lodel.exceptions import *
8
-from lodel.settings.validator import *
8
+from lodel.validator.validator import *
9 9
 
10
-class SettingValidatorTestCase(unittest.TestCase):
10
+class ValidatorTestCase(unittest.TestCase):
11 11
     
12 12
     def test_init_basic(self):
13 13
         """ Testing the SettingsValidator class instanciation"""
14
-        valid = SettingValidator('string')
14
+        valid = Validator('string')
15 15
         #trying to call it
16 16
         valid('test')
17 17
 
18 18
     def test_init_badname(self):
19
-        """ Testing SettingValidator instanciation with non existing validator
19
+        """ Testing Validator instanciation with non existing validator
20 20
             name"""
21 21
         with self.assertRaises(LodelFatalError):
22
-            SettingValidator('qklfhsdufgsdyfugigsdfsdlcknsdp')
22
+            Validator('qklfhsdufgsdyfugigsdfsdlcknsdp')
23 23
 
24 24
     def test_noneswitch(self):
25 25
         """ Testing the none_is_valid switch given at validator instanciation
26 26
         """
27
-        none_invalid = SettingValidator('int')
28
-        none_valid = SettingValidator('int', none_is_valid = True)
27
+        none_invalid = Validator('int')
28
+        none_valid = Validator('int', none_is_valid = True)
29 29
 
30 30
         none_valid(None)
31
-        with self.assertRaises(SettingsValidationError):
31
+        with self.assertRaises(ValidationError):
32 32
             none_invalid(None)
33 33
 
34 34
     def test_validator_registration(self):
35
-        """ Testing the register_validator method of SettingValidator """
35
+        """ Testing the register_validator method of Validator """
36 36
         mockfun = mock.MagicMock()
37 37
         vname = 'lfkjdshfkuhsdygsuuyfsduyf'
38 38
         testval = 'foo'
39
-        SettingValidator.register_validator(vname, mockfun, 'test validator')
39
+        Validator.register_validator(vname, mockfun, 'test validator')
40 40
         #Using registered validator
41
-        valid = SettingValidator(vname)
41
+        valid = Validator(vname)
42 42
         valid(testval)
43 43
         mockfun.assert_called_once_with(testval)
44 44
 
45 45
     def test_validator_optargs_forwarding(self):
46
-        """ Testing the ability for SettingValidator to forward optional
46
+        """ Testing the ability for Validator to forward optional
47 47
             arguments """
48 48
         mockfun = mock.MagicMock()
49 49
         vname = 'lkjdsfhsdiufhisduguig'
50 50
         testval = 'azertyuiop'
51
-        SettingValidator.register_validator(vname, mockfun, 'test validator')
51
+        Validator.register_validator(vname, mockfun, 'test validator')
52 52
         #Using registered validator with more arguments
53
-        valid = SettingValidator(vname,
53
+        valid = Validator(vname,
54 54
             arga = 'a', argb = 42, argc = '1337')
55 55
         valid(testval)
56 56
         mockfun.assert_called_once_with(

Loading…
Cancel
Save