refactor validation
[tiramisu.git] / tiramisu / storage / dictionary / option.py
1 # -*- coding: utf-8 -*-
2 ""
3 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # ____________________________________________________________
20 from tiramisu.i18n import _
21 from tiramisu.setting import undefined
22 from tiramisu.error import ConfigError
23
24
25 #____________________________________________________________
26 #
27 # Base
28 #('_name', '_informations', '_multi', '_multitype', '_warnings_only', '_extra', '_readonly', '_subdyn)
29 class StorageBase(object):
30     __slots__ = ('_name',
31                  '_informations',
32                  '_multi',
33                  '_extra',
34                  '_warnings_only',
35                  #valeur
36                  '_default',
37                  '_default_multi',
38                  #calcul
39                  '_subdyn',
40                  '_requires',
41                  '_properties',
42                  '_calc_properties',
43                  '_val_call',
44                  #'_validator',
45                  #'_validator_params',
46                  #'_callback',
47                  #'_callback_params',
48                  '_consistencies',
49                  '_master_slaves',
50                  '_choice_values',
51                  '_choice_values_params',
52                  #autre
53                  '_state_master_slaves',
54                  '_state_callback',
55                  '_state_callback_params',
56                  '_state_requires',
57                  '_stated',
58                  '_state_consistencies',
59                  '_state_informations',
60                  '_state_readonly',
61                  '__weakref__'
62                  )
63
64     def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
65                  requires, properties, opt=undefined):
66         self._name = name
67         if doc is not undefined:
68             self._informations = {'doc': doc}
69             if multi != 1:
70                 self._multi = multi
71             if extra is not None:
72                 self._extra = extra
73         if warnings_only is True:
74             self._warnings_only = warnings_only
75
76         if calc_properties is not undefined:
77             self._calc_properties = calc_properties
78         if requires is not undefined:
79             self._requires = requires
80         if properties is not undefined:
81             self._properties = properties
82         if opt is not undefined:
83             self._opt = opt
84
85     def _set_default_values(self, default, default_multi):
86         if ((self.impl_is_multi() and default != tuple()) or
87                 (not self.impl_is_multi() and default is not None)):
88             if self.impl_is_multi():
89                 default = tuple(default)
90             self._default = default
91
92         if self.impl_is_multi() and default_multi is not None:
93             try:
94                 self._validate(default_multi)
95             except ValueError as err:  # pragma: optional cover
96                 raise ValueError(_("invalid default_multi value {0} "
97                                    "for option {1}: {2}").format(
98                                        str(default_multi),
99                                        self.impl_getname(), err))
100             self._default_multi = default_multi
101
102     # information
103     def impl_set_information(self, key, value):
104         """updates the information's attribute
105         (which is a dictionary)
106
107         :param key: information's key (ex: "help", "doc"
108         :param value: information's value (ex: "the help string")
109         """
110         self._informations[key] = value
111
112     def impl_get_information(self, key, default=undefined):
113         """retrieves one information's item
114
115         :param key: the item string (ex: "help")
116         """
117         error = False
118         dico = self._informations
119         if dico is None or isinstance(dico, str) or isinstance(dico, unicode):
120             if key == 'doc':
121                 return dico
122             error = True
123         elif isinstance(dico, tuple):
124             try:
125                 return dico[1][dico[0].index(key)]
126             except AttributeError:
127                 if default is not undefined:
128                     return default
129                 error = True
130         else:
131             if default is not undefined:
132                 return self._informations.get(key, default)
133             try:
134                 return self._informations[key]
135             except KeyError:  # pragma: optional cover
136                 error = True
137         if error:
138             raise ValueError(_("information's item not found: {0}").format(
139                 key))
140
141     def _add_consistency(self, func, all_cons_opts, params):
142         cons = (func, all_cons_opts, params)
143         try:
144             self._consistencies.append(cons)
145         except AttributeError:
146             self._consistencies = [cons]
147
148     def _get_consistencies(self):
149         try:
150             return self._consistencies
151         except AttributeError:
152             return tuple()
153
154     def _set_callback(self, callback, callback_params):
155         if callback_params is None or callback_params == {}:
156             val_call = (callback,)
157         else:
158             val_call = tuple([callback, callback_params])
159         try:
160             self._val_call = (self._val_call[0], val_call)
161         except AttributeError:
162             self._val_call = (None, val_call)
163
164     def impl_get_callback(self):
165         default = (None, {})
166         try:
167             call = self._val_call[1]
168         except (AttributeError, IndexError):
169             ret_call = default
170         else:
171             if call is None:
172                 ret_call = default
173             else:
174                 if len(call) == 1:
175                     ret_call = (call[0], default[1])
176                 else:
177                     ret_call = call
178         return ret_call
179
180     def impl_get_calc_properties(self):
181         try:
182             return self._calc_properties
183         except AttributeError:
184             return frozenset()
185
186     def impl_getrequires(self):
187         try:
188             return self._requires
189         except AttributeError:
190             return []
191
192     def _set_validator(self, validator, validator_params):
193         if validator_params is None:
194             val_call = (validator,)
195         else:
196             val_call = (validator, validator_params)
197         try:
198             self._val_call = (val_call, self._val_call[1])
199         except (AttributeError, IndexError):
200             self._val_call = (val_call, None)
201
202     def impl_get_validator(self):
203         default = (None, {})
204         try:
205             val = self._val_call[0]
206         except (AttributeError, IndexError):
207             ret_val = default
208         else:
209             if val is None:
210                 ret_val = default
211             else:
212                 if len(val) == 1:
213                     ret_val = (val[0], default[1])
214                 else:
215                     ret_val = val
216         return ret_val
217
218     def _get_id(self):
219         return id(self)
220
221     def _impl_getsubdyn(self):
222         return self._subdyn
223
224     def _impl_getopt(self):
225         return self._opt
226
227     def _set_readonly(self):
228         if not self.impl_is_readonly():
229             dico = self._informations
230             if not (dico is None or isinstance(dico, str) or isinstance(dico, unicode)):
231                 keys = tuple(dico.keys())
232                 if keys == ('doc',):
233                     dico = dico['doc']
234                 else:
235                     dico = tuple([tuple(dico.keys()), tuple(dico.values())])
236             self._informations = dico
237             try:
238                 extra = self._extra
239                 self._extra = tuple([tuple(extra.keys()), tuple(extra.values())])
240             except AttributeError:
241                 pass
242
243     def _impl_setsubdyn(self, subdyn):
244         self._subdyn = subdyn
245
246     def _impl_setopt(self, opt):
247         self._opt = opt
248
249     def _impl_convert_informations(self, descr, load=False):
250         if not load:
251             infos = self._informations
252             if isinstance(infos, tuple):
253                 self._state_informations = {}
254                 for key, value in infos:
255                     self._state_informations[key] = value
256             elif isinstance(infos, str) or isinstance(infos, unicode):
257                 self._state_informations = {'doc': infos}
258             else:
259                 self._state_informations = infos
260             self._state_readonly = self.impl_is_readonly()
261         else:
262             try:
263                 self._informations = self._state_informations
264                 del(self._state_informations)
265             except AttributeError:
266                 pass
267             if self._state_readonly:
268                 self._set_readonly()
269             del(self._state_readonly)
270
271     def _impl_getattributes(self):
272         slots = set()
273         for subclass in self.__class__.__mro__:
274             if subclass is not object:
275                 slots.update(subclass.__slots__)
276         return slots
277
278     def impl_is_readonly(self):
279         try:
280             return not isinstance(self._informations, dict)
281         except AttributeError:
282             return False
283
284     def impl_getname(self):
285         return self._name
286
287     def impl_is_multi(self):
288         try:
289             _multi = self._multi
290         except AttributeError:
291             _multi = 1
292         return _multi == 0 or _multi == 2
293
294     def impl_is_submulti(self):
295         try:
296             _multi = self._multi
297         except IndexError:
298             _multi = 1
299         return _multi == 2
300
301     def _get_extra(self, key):
302         extra = self._extra
303         if isinstance(extra, tuple):
304             return extra[1][extra[0].index(key)]
305         else:
306             return extra[key]
307
308     def _is_warnings_only(self):
309         try:
310             return self._warnings_only
311         except:
312             return False
313
314     def impl_getdefault(self):
315         "accessing the default value"
316         try:
317             ret = self._default
318             if self.impl_is_multi():
319                 return list(ret)
320             return ret
321         except AttributeError:
322             if self.impl_is_multi():
323                 return []
324             return None
325
326     def impl_getdefault_multi(self):
327         "accessing the default value for a multi"
328         if self.impl_is_multi():
329             try:
330                 return self._default_multi
331             except (AttributeError, IndexError):
332                 pass
333
334     def commit(self):
335         pass
336
337
338 class StorageOptionDescription(StorageBase):
339     __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
340                  '_group_type', '_is_build_cache', '_state_group_type')
341
342     def __init__(self, name, multi, warnings_only, doc, extra):
343         super(StorageOptionDescription, self).__init__(name, multi,
344                                                        warnings_only, doc,
345                                                        None, undefined,
346                                                        undefined, undefined)
347         self._cache_paths = None
348
349     def _add_children(self, child_names, children):
350         self._children = (tuple(child_names), tuple(children))
351
352     def impl_already_build_caches(self):
353         return self._is_build_cache
354
355     def impl_get_opt_by_path(self, path):
356         try:
357             return self._cache_paths[0][self._cache_paths[1].index(path)]
358         except ValueError:  # pragma: optional cover
359             raise AttributeError(_('no option for path {0}').format(path))
360
361     def impl_get_path_by_opt(self, opt):
362         try:
363             return self._cache_paths[1][self._cache_paths[0].index(opt)]
364         except ValueError:  # pragma: optional cover
365             raise AttributeError(_('no option {0} found').format(opt))
366
367     def impl_get_group_type(self):  # pragma: optional cover
368         return self._group_type
369
370     def impl_build_cache_option(self, _currpath=None, cache_path=None,
371                                 cache_option=None):
372         try:
373             self._cache_paths
374         except AttributeError:
375             self._cache_paths = None
376         if _currpath is None and self._cache_paths is not None:  # pragma: optional cover
377             # cache already set
378             return
379         if _currpath is None:
380             save = True
381             _currpath = []
382         else:
383             save = False
384         if cache_path is None:
385             cache_path = []
386             cache_option = []
387         for option in self._impl_getchildren(dyn=False):
388             attr = option.impl_getname()
389             path = str('.'.join(_currpath + [attr]))
390             cache_option.append(option)
391             cache_path.append(path)
392             if option.impl_is_optiondescription():
393                 _currpath.append(attr)
394                 option.impl_build_cache_option(_currpath, cache_path,
395                                                cache_option)
396                 _currpath.pop()
397         if save:
398             self._cache_paths = (tuple(cache_option), tuple(cache_path))
399             self._is_build_cache = True
400
401     def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context):
402         find_results = []
403
404         def _rebuild_dynpath(path, suffix, dynopt):
405             found = False
406             spath = path.split('.')
407             for length in xrange(1, len(spath)):
408                 subpath = '.'.join(spath[0:length])
409                 subopt = self.impl_get_opt_by_path(subpath)
410                 if dynopt == subopt:
411                     found = True
412                     break
413             if not found:
414                 raise ConfigError(_('cannot find dynpath'))
415             subpath = subpath + suffix
416             for slength in xrange(length, len(spath)):
417                 subpath = subpath + '.' + spath[slength] + suffix
418             return subpath
419
420         def _filter_by_name(path, option):
421             name = option.impl_getname()
422             if option._is_subdyn():
423                 if byname.startswith(name):
424                     found = False
425                     for suffix in option._subdyn._impl_get_suffixes(
426                             context):
427                         if byname == name + suffix:
428                             found = True
429                             path = _rebuild_dynpath(path, suffix,
430                                                     option._subdyn)
431                             option = option._impl_to_dyn(
432                                 name + suffix, path)
433                             break
434                     if not found:
435                         return False
436             else:
437                 if not byname == name:
438                     return False
439             find_results.append((path, option))
440             return True
441
442         def _filter_by_type(path, option):
443             if isinstance(option, bytype):
444                 #if byname is not None, check option byname in _filter_by_name
445                 #not here
446                 if byname is None:
447                     if option._is_subdyn():
448                         name = option.impl_getname()
449                         for suffix in option._subdyn._impl_get_suffixes(
450                                 context):
451                             spath = _rebuild_dynpath(path, suffix,
452                                                      option._subdyn)
453                             find_results.append((spath, option._impl_to_dyn(
454                                 name + suffix, spath)))
455                     else:
456                         find_results.append((path, option))
457                 return True
458             return False
459
460         def _filter(path, option):
461             if bytype is not None:
462                 retval = _filter_by_type(path, option)
463                 if byname is None:
464                     return retval
465             if byname is not None:
466                 return _filter_by_name(path, option)
467
468         opts, paths = self._cache_paths
469         for index in range(0, len(paths)):
470             option = opts[index]
471             if option.impl_is_optiondescription():
472                 continue
473             path = paths[index]
474             if _subpath is not None and not path.startswith(_subpath + '.'):
475                 continue
476             if bytype == byname is None:
477                 if option._is_subdyn():
478                     name = option.impl_getname()
479                     for suffix in option._subdyn._impl_get_suffixes(
480                             context):
481                         spath = _rebuild_dynpath(path, suffix,
482                                                  option._subdyn)
483                         find_results.append((spath, option._impl_to_dyn(
484                             name + suffix, spath)))
485                 else:
486                     find_results.append((path, option))
487             else:
488                 if _filter(path, option) is False:
489                     continue
490             if only_first:
491                 return find_results[0]
492         return find_results
493
494     def _impl_st_getchildren(self, context, only_dyn=False):
495         for child in self._children[1]:
496             if only_dyn is False or child.impl_is_dynoptiondescription():
497                 yield(child)
498
499     def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
500         error = False
501         if suffix is not undefined:
502             try:
503                 if undefined in [suffix, context]:  # pragma: optional cover
504                     raise ConfigError(_("suffix and context needed if "
505                                         "it's a dyn option"))
506                 if name.endswith(suffix):
507                     oname = name[:-len(suffix)]
508                     child = self._children[1][self._children[0].index(oname)]
509                     return self._impl_get_dynchild(child, suffix)
510                 else:
511                     error = True
512             except ValueError:  # pragma: optional cover
513                 error = True
514         else:
515             try:  # pragma: optional cover
516                 if name == '_readonly':
517                     raise AttributeError(_("{0} instance has no attribute "
518                                          "'_readonly'").format(
519                                              self.__class__.__name__))
520                 child = self._children[1][self._children[0].index(name)]
521                 if dyn and child.impl_is_dynoptiondescription():
522                     error = True
523                 else:
524                     return child
525             except ValueError:
526                 child = self._impl_search_dynchild(name, context=context)
527                 if child != []:
528                     return child
529                 error = True
530         if error:
531             raise AttributeError(_('unknown Option {0} '
532                                    'in OptionDescription {1}'
533                                    '').format(name, self.impl_getname()))
534
535     def _get_force_store_value(self):
536         #FIXME faire des tests (notamment pas ajouter à un config)
537         #FIXME devrait faire un cache !
538         opts, paths = self._cache_paths
539         for index in range(0, len(paths)):
540             opt = opts[index]
541             path = paths[index]
542             if 'force_store_value' in opt._properties:
543                 yield (opt, path)