add SubMulti
[tiramisu.git] / tiramisu / option / baseoption.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
3 #
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Lesser General Public License as published by the
6 # Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
12 # details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # The original `Config` design model is unproudly borrowed from
18 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
21 import re
22 from copy import copy
23 from types import FunctionType
24 import warnings
25
26 from tiramisu.i18n import _
27 from tiramisu.setting import log, undefined
28 from tiramisu.autolib import carry_out_calculation
29 from tiramisu.error import ConfigError, ValueWarning
30 from tiramisu.storage import get_storages_option
31
32
33 StorageBase = get_storages_option('base')
34
35
36 class SubMulti(object):
37     pass
38
39
40 submulti = SubMulti()
41
42
43 name_regexp = re.compile(r'^\d+')
44 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
45                    'make_dict', 'unwrap_from_path', 'read_only',
46                    'read_write', 'getowner', 'set_contexts')
47
48
49 def valid_name(name):
50     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
51     if not isinstance(name, str):
52         return False
53     if re.match(name_regexp, name) is None and not name.startswith('_') \
54             and name not in forbidden_names \
55             and not name.startswith('impl_') \
56             and not name.startswith('cfgimpl_'):
57         return True
58     else:
59         return False
60
61
62 def validate_callback(callback, callback_params, type_):
63     if type(callback) != FunctionType:
64         raise ValueError(_('{0} must be a function').format(type_))
65     if callback_params is not None:
66         if not isinstance(callback_params, dict):
67             raise ValueError(_('{0}_params must be a dict').format(type_))
68         for key, callbacks in callback_params.items():
69             if key != '' and len(callbacks) != 1:
70                 raise ValueError(_("{0}_params with key {1} mustn't have "
71                                    "length different to 1").format(type_,
72                                                                    key))
73             if not isinstance(callbacks, tuple):
74                 raise ValueError(_('{0}_params must be tuple for key "{1}"'
75                                    ).format(type_, key))
76             for callbk in callbacks:
77                 if isinstance(callbk, tuple):
78                     if len(callbk) == 1:
79                         if callbk != (None,):
80                             raise ValueError(_('{0}_params with length of '
81                                                'tuple as 1 must only have '
82                                                'None as first value'))
83                     elif len(callbk) != 2:
84                         raise ValueError(_('{0}_params must only have 1 or 2 '
85                                            'as length'))
86                     else:
87                         option, force_permissive = callbk
88                         if type_ == 'validator' and not force_permissive:
89                             raise ValueError(_('validator not support tuple'))
90                         if not isinstance(option, Option) and not \
91                                 isinstance(option, SymLinkOption):
92                             raise ValueError(_('{0}_params must have an option'
93                                                ' not a {0} for first argument'
94                                                ).format(type_, type(option)))
95                         if force_permissive not in [True, False]:
96                             raise ValueError(_('{0}_params must have a boolean'
97                                                ' not a {0} for second argument'
98                                                ).format(type_, type(
99                                                    force_permissive)))
100 #____________________________________________________________
101 #
102
103
104 class Base(StorageBase):
105     __slots__ = tuple()
106
107     def __init__(self, name, doc, default=None, default_multi=None,
108                  requires=None, multi=False, callback=None,
109                  callback_params=None, validator=None, validator_params=None,
110                  properties=None, warnings_only=False):
111         if not valid_name(name):
112             raise ValueError(_("invalid name: {0} for option").format(name))
113         self._name = name
114         self._readonly = False
115         self._informations = {}
116         self.impl_set_information('doc', doc)
117         if requires is not None:
118             self._calc_properties, self._requires = validate_requires_arg(
119                 requires, self._name)
120         else:
121             self._calc_properties = frozenset()
122             self._requires = []
123         if not multi and default_multi is not None:
124             raise ValueError(_("a default_multi is set whereas multi is False"
125                              " in option: {0}").format(name))
126         if default_multi is not None:
127             try:
128                 self._validate(default_multi)
129             except ValueError as err:
130                 raise ValueError(_("invalid default_multi value {0} "
131                                    "for option {1}: {2}").format(
132                                        str(default_multi), name, err))
133         self._multi = multi
134         if self._multi is not False:
135             if default is None:
136                 default = []
137             self._default_multi = default_multi
138         if callback is not None and ((not multi and (default is not None or
139                                                      default_multi is not None))
140                                      or (multi and (default != [] or
141                                                     default_multi is not None))
142                                      ):
143             raise ValueError(_("default value not allowed if option: {0} "
144                              "is calculated").format(name))
145         if properties is None:
146             properties = tuple()
147         if not isinstance(properties, tuple):
148             raise TypeError(_('invalid properties type {0} for {1},'
149                             ' must be a tuple').format(
150                                 type(properties),
151                                 self._name))
152         if validator is not None:
153             validate_callback(validator, validator_params, 'validator')
154             self._validator = validator
155             if validator_params is not None:
156                 self._validator_params = validator_params
157         if callback is None and callback_params is not None:
158             raise ValueError(_("params defined for a callback function but "
159                              "no callback defined"
160                              " yet for option {0}").format(name))
161         if callback is not None:
162             validate_callback(callback, callback_params, 'callback')
163             self._callback = callback
164             if callback_params is not None:
165                 self._callback_params = callback_params
166         if self._calc_properties != frozenset([]) and properties is not tuple():
167             set_forbidden_properties = self._calc_properties & set(properties)
168             if set_forbidden_properties != frozenset():
169                 raise ValueError('conflict: properties already set in '
170                                  'requirement {0}'.format(
171                                      list(set_forbidden_properties)))
172         if multi and default is None:
173             self._default = []
174         else:
175             self._default = default
176         self._properties = properties
177         self._warnings_only = warnings_only
178         ret = super(Base, self).__init__()
179         self.impl_validate(self._default)
180         return ret
181
182
183 class BaseOption(Base):
184     """This abstract base class stands for attribute access
185     in options that have to be set only once, it is of course done in the
186     __setattr__ method
187     """
188     __slots__ = tuple()
189
190     # information
191     def impl_set_information(self, key, value):
192         """updates the information's attribute
193         (which is a dictionary)
194
195         :param key: information's key (ex: "help", "doc"
196         :param value: information's value (ex: "the help string")
197         """
198         self._informations[key] = value
199
200     def impl_get_information(self, key, default=undefined):
201         """retrieves one information's item
202
203         :param key: the item string (ex: "help")
204         """
205         if key in self._informations:
206             return self._informations[key]
207         elif default is not undefined:
208             return default
209         else:
210             raise ValueError(_("information's item not found: {0}").format(
211                 key))
212
213     # ____________________________________________________________
214     # serialize object
215     def _impl_convert_requires(self, descr, load=False):
216         """export of the requires during the serialization process
217
218         :type descr: :class:`tiramisu.option.OptionDescription`
219         :param load: `True` if we are at the init of the option description
220         :type load: bool
221         """
222         if not load and self._requires is None:
223             self._state_requires = None
224         elif load and self._state_requires is None:
225             self._requires = None
226             del(self._state_requires)
227         else:
228             if load:
229                 _requires = self._state_requires
230             else:
231                 _requires = self._requires
232             new_value = []
233             for requires in _requires:
234                 new_requires = []
235                 for require in requires:
236                     if load:
237                         new_require = [descr.impl_get_opt_by_path(require[0])]
238                     else:
239                         new_require = [descr.impl_get_path_by_opt(require[0])]
240                     new_require.extend(require[1:])
241                     new_requires.append(tuple(new_require))
242                 new_value.append(tuple(new_requires))
243             if load:
244                 del(self._state_requires)
245                 self._requires = new_value
246             else:
247                 self._state_requires = new_value
248
249     # serialize
250     def _impl_getstate(self, descr):
251         """the under the hood stuff that need to be done
252         before the serialization.
253
254         :param descr: the parent :class:`tiramisu.option.OptionDescription`
255         """
256         self._stated = True
257         for func in dir(self):
258             if func.startswith('_impl_convert_'):
259                 getattr(self, func)(descr)
260         self._state_readonly = self._readonly
261
262     def __getstate__(self, stated=True):
263         """special method to enable the serialization with pickle
264         Usualy, a `__getstate__` method does'nt need any parameter,
265         but somme under the hood stuff need to be done before this action
266
267         :parameter stated: if stated is `True`, the serialization protocol
268                            can be performed, not ready yet otherwise
269         :parameter type: bool
270         """
271         try:
272             self._stated
273         except AttributeError:
274             raise SystemError(_('cannot serialize Option, '
275                                 'only in OptionDescription'))
276         slots = set()
277         for subclass in self.__class__.__mro__:
278             if subclass is not object:
279                 slots.update(subclass.__slots__)
280         slots -= frozenset(['_cache_paths', '_cache_consistencies',
281                             '__weakref__'])
282         states = {}
283         for slot in slots:
284             # remove variable if save variable converted
285             # in _state_xxxx variable
286             if '_state' + slot not in slots:
287                 try:
288                     if slot.startswith('_state'):
289                         states[slot] = getattr(self, slot)
290                         # remove _state_xxx variable
291                         self.__delattr__(slot)
292                     else:
293                         states[slot] = getattr(self, slot)
294                 except AttributeError:
295                     pass
296         if not stated:
297             del(states['_stated'])
298         return states
299
300     # unserialize
301     def _impl_setstate(self, descr):
302         """the under the hood stuff that need to be done
303         before the serialization.
304
305         :type descr: :class:`tiramisu.option.OptionDescription`
306         """
307         for func in dir(self):
308             if func.startswith('_impl_convert_'):
309                 getattr(self, func)(descr, load=True)
310         try:
311             self._readonly = self._state_readonly
312             del(self._state_readonly)
313             del(self._stated)
314         except AttributeError:
315             pass
316
317     def __setstate__(self, state):
318         """special method that enables us to serialize (pickle)
319
320         Usualy, a `__setstate__` method does'nt need any parameter,
321         but somme under the hood stuff need to be done before this action
322
323         :parameter state: a dict is passed to the loads, it is the attributes
324                           of the options object
325         :type state: dict
326         """
327         for key, value in state.items():
328             setattr(self, key, value)
329
330     def __setattr__(self, name, value):
331         """set once and only once some attributes in the option,
332         like `_name`. `_name` cannot be changed one the option and
333         pushed in the :class:`tiramisu.option.OptionDescription`.
334
335         if the attribute `_readonly` is set to `True`, the option is
336         "frozen" (which has noting to do with the high level "freeze"
337         propertie or "read_only" property)
338         """
339         if name not in ('_option', '_is_build_cache') \
340                 and not isinstance(value, tuple) and \
341                 not name.startswith('_state'):
342             is_readonly = False
343             # never change _name
344             if name == '_name':
345                 try:
346                     if self._name is not None:
347                         #so _name is already set
348                         is_readonly = True
349                 except (KeyError, AttributeError):
350                     pass
351             elif name != '_readonly':
352                 is_readonly = self.impl_is_readonly()
353             if is_readonly:
354                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
355                                        " read-only").format(
356                                            self.__class__.__name__,
357                                            self._name,
358                                            name))
359         super(BaseOption, self).__setattr__(name, value)
360
361     def impl_is_readonly(self):
362         try:
363             if self._readonly is True:
364                 return True
365         except AttributeError:
366             pass
367         return False
368
369     def impl_getname(self):
370         return self._name
371
372
373 class OnlyOption(BaseOption):
374     __slots__ = tuple()
375
376
377 class Option(OnlyOption):
378     """
379     Abstract base class for configuration option's.
380
381     Reminder: an Option object is **not** a container for the value.
382     """
383 #    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
384 #                 '_state_callback', '_callback',
385 #                 '_consistencies', '_warnings_only', '_master_slaves',
386 #                 '_state_consistencies', '__weakref__')
387     __slots__ = tuple()
388     _empty = ''
389
390     def __init__(self, name, doc, default=None, default_multi=None,
391                  requires=None, multi=False, callback=None,
392                  callback_params=None, validator=None, validator_params=None,
393                  properties=None, warnings_only=False):
394         """
395         :param name: the option's name
396         :param doc: the option's description
397         :param default: specifies the default value of the option,
398                         for a multi : ['bla', 'bla', 'bla']
399         :param default_multi: 'bla' (used in case of a reset to default only at
400                         a given index)
401         :param requires: is a list of names of options located anywhere
402                          in the configuration.
403         :param multi: if true, the option's value is a list
404         :param callback: the name of a function. If set, the function's output
405                          is responsible of the option's value
406         :param callback_params: the callback's parameter
407         :param validator: the name of a function which stands for a custom
408                           validation of the value
409         :param validator_params: the validator's parameters
410         :param properties: tuple of default properties
411         :param warnings_only: _validator and _consistencies don't raise if True
412                              Values()._warning contain message
413
414         """
415         super(Option, self).__init__(name, doc, default, default_multi,
416                                      requires, multi, callback,
417                                      callback_params, validator,
418                                      validator_params, properties,
419                                      warnings_only)
420
421     def impl_getrequires(self):
422         return self._requires
423
424     def _launch_consistency(self, func, option, value, context, index,
425                             submulti_index, all_cons_opts, warnings_only):
426         """Launch consistency now
427
428         :param func: function name, this name should start with _cons_
429         :type func: `str`
430         :param option: option that value is changing
431         :type option: `tiramisu.option.Option`
432         :param value: new value of this option
433         :param context: Config's context, if None, check default value instead
434         :type context: `tiramisu.config.Config`
435         :param index: only for multi option, consistency should be launch for
436                       specified index
437         :type index: `int`
438         :param all_cons_opts: all options concerne by this consistency
439         :type all_cons_opts: `list` of `tiramisu.option.Option`
440         :param warnings_only: specific raise error for warning
441         :type warnings_only: `boolean`
442         """
443         if context is not None:
444             descr = context.cfgimpl_get_description()
445
446         all_cons_vals = []
447         for opt in all_cons_opts:
448             #get value
449             if option == opt:
450                 opt_value = value
451             else:
452                 #if context, calculate value, otherwise get default value
453                 if context is not None:
454                     opt_value = context.getattr(
455                         descr.impl_get_path_by_opt(opt), validate=False,
456                         force_permissive=True)
457                 else:
458                     opt_value = opt.impl_getdefault()
459
460             #append value
461             if not self.impl_is_multi() or option == opt:
462                 all_cons_vals.append(opt_value)
463             elif self.impl_is_submulti():
464                 try:
465                     all_cons_vals.append(opt_value[index][submulti_index])
466                 except IndexError:
467                     #value is not already set, could be higher index
468                     #so return if no value
469                     return
470             else:
471                 try:
472                     all_cons_vals.append(opt_value[index])
473                 except IndexError:
474                     #value is not already set, could be higher index
475                     #so return if no value
476                     return
477         getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
478
479     def impl_validate(self, value, context=None, validate=True,
480                       force_index=None, force_submulti_index=None):
481         """
482         :param value: the option's value
483         :param context: Config's context
484         :type context: :class:`tiramisu.config.Config`
485         :param validate: if true enables ``self._validator`` validation
486         :type validate: boolean
487         :param force_index: if multi, value has to be a list
488                             not if force_index is not None
489         :type force_index: integer
490         :param force_submulti_index: if submulti, value has to be a list
491                                      not if force_submulti_index is not None
492         :type force_submulti_index: integer
493         """
494         if not validate:
495             return
496
497         def val_validator(val):
498             if self._validator is not None:
499                 if self._validator_params is not None:
500                     validator_params = {}
501                     for val_param, values in self._validator_params.items():
502                         validator_params[val_param] = values
503                     #FIXME ... ca sert à quoi ...
504                     if '' in validator_params:
505                         lst = list(validator_params[''])
506                         lst.insert(0, val)
507                         validator_params[''] = tuple(lst)
508                     else:
509                         validator_params[''] = (val,)
510                 else:
511                     validator_params = {'': (val,)}
512                 # Raise ValueError if not valid
513                 carry_out_calculation(self, config=context,
514                                       callback=self._validator,
515                                       callback_params=validator_params)
516
517         def do_validation(_value, _index, submulti_index):
518             if _value is None:
519                 return
520             # option validation
521             try:
522                 self._validate(_value)
523             except ValueError as err:
524                 log.debug('do_validation: value: {0}, index: {1}, '
525                           'submulti_index: {2}'.format(_value, _index,
526                                                        submulti_index),
527                           exc_info=True)
528                 raise ValueError(_('invalid value for option {0}: {1}'
529                                    '').format(self.impl_getname(), err))
530             error = None
531             warning = None
532             try:
533                 # valid with self._validator
534                 val_validator(_value)
535                 # if not context launch consistency validation
536                 if context is not None:
537                     descr._valid_consistency(self, _value, context, _index,
538                                              submulti_index)
539                 self._second_level_validation(_value, self._warnings_only)
540             except ValueError as error:
541                 log.debug(_('do_validation for {0}: error in value').format(
542                     self.impl_getname()), exc_info=True)
543                 if self._warnings_only:
544                     warning = error
545                     error = None
546             except ValueWarning as warning:
547                 log.debug(_('do_validation for {0}: warning in value').format(
548                     self.impl_getname()), exc_info=True)
549                 pass
550             if error is None and warning is None:
551                 try:
552                     # if context launch consistency validation
553                     if context is not None:
554                         descr._valid_consistency(self, _value, context, _index,
555                                                  submulti_index)
556                 except ValueError as error:
557                     log.debug(_('do_validation for {0}: error in consistency').format(
558                         self.impl_getname()), exc_info=True)
559                     pass
560                 except ValueWarning as warning:
561                     log.debug(_('do_validation for {0}: warning in consistency').format(
562                         self.impl_getname()), exc_info=True)
563                     pass
564             if warning:
565                 msg = _("warning on the value of the option {0}: {1}").format(
566                     self.impl_getname(), warning)
567                 warnings.warn_explicit(ValueWarning(msg, self),
568                                        ValueWarning,
569                                        self.__class__.__name__, 0)
570             elif error:
571                 raise ValueError(_("invalid value for option {0}: {1}").format(
572                     self._name, error))
573
574         # generic calculation
575         if context is not None:
576             descr = context.cfgimpl_get_description()
577
578         if not self.impl_is_multi():
579             do_validation(value, None, None)
580         elif force_index is not None:
581             if self.impl_is_submulti() and force_submulti_index is None:
582                 if not isinstance(value, list):
583                     raise ValueError(_("invalid value {0} for option {1} which"
584                                        " must be a list").format(
585                                            value, self.impl_getname()))
586                 for idx, val in enumerate(value):
587                     do_validation(val, force_index, idx)
588             else:
589                 do_validation(value, force_index, force_submulti_index)
590         else:
591             if not isinstance(value, list):
592                 raise ValueError(_("invalid value {0} for option {1} which "
593                                    "must be a list").format(value,
594                                                             self.impl_getname()))
595             for idx, val in enumerate(value):
596                 if self.impl_is_submulti() and force_submulti_index is None:
597                     if not isinstance(val, list):
598                         raise ValueError(_("invalid value {0} for option {1} "
599                                            "which must be a list of list"
600                                            "").format(value,
601                                                       self.impl_getname()))
602                     for slave_idx, slave_val in enumerate(val):
603                         do_validation(slave_val, idx, slave_idx)
604                 else:
605                     do_validation(val, idx, force_submulti_index)
606
607     def impl_getdefault(self):
608         "accessing the default value"
609         if isinstance(self._default, list):
610             return copy(self._default)
611         return self._default
612
613     def impl_getdefault_multi(self):
614         "accessing the default value for a multi"
615         return self._default_multi
616
617     def impl_is_master_slaves(self, type_='both'):
618         """FIXME
619         """
620         try:
621             self._master_slaves
622             if type_ in ('both', 'master') and \
623                     self._master_slaves.is_master(self):
624                 return True
625             if type_ in ('both', 'slave') and \
626                     not self._master_slaves.is_master(self):
627                 return True
628         except:
629             pass
630         return False
631
632     def impl_get_master_slaves(self):
633         return self._master_slaves
634
635     def impl_is_empty_by_default(self):
636         "no default value has been set yet"
637         if ((not self.impl_is_multi() and self._default is None) or
638                 (self.impl_is_multi() and (self._default == []
639                                            or None in self._default))):
640             return True
641         return False
642
643     def impl_getdoc(self):
644         "accesses the Option's doc"
645         return self.impl_get_information('doc')
646
647     def impl_has_callback(self):
648         "to know if a callback has been defined or not"
649         if self._callback is None:
650             return False
651         else:
652             return True
653
654     def impl_get_callback(self):
655         return self._callback, self._callback_params
656
657     #def impl_getkey(self, value):
658     #    return value
659
660     def impl_is_multi(self):
661         return self._multi is True or self._multi is submulti
662
663     def impl_is_submulti(self):
664         return self._multi is submulti
665
666     def impl_add_consistency(self, func, *other_opts, **params):
667         """Add consistency means that value will be validate with other_opts
668         option's values.
669
670         :param func: function's name
671         :type func: `str`
672         :param other_opts: options used to validate value
673         :type other_opts: `list` of `tiramisu.option.Option`
674         :param params: extra params (only warnings_only are allowed)
675         """
676         if self.impl_is_readonly():
677             raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
678                                    " read-only").format(
679                                        self.__class__.__name__,
680                                        self._name))
681         warnings_only = params.get('warnings_only', False)
682         for opt in other_opts:
683             if not isinstance(opt, Option):
684                 raise ConfigError(_('consistency must be set with an option'))
685             if self is opt:
686                 raise ConfigError(_('cannot add consistency with itself'))
687             if self.impl_is_multi() != opt.impl_is_multi():
688                 raise ConfigError(_('every options in consistency must be '
689                                     'multi or none'))
690         func = '_cons_{0}'.format(func)
691         all_cons_opts = tuple([self] + list(other_opts))
692         value = self.impl_getdefault()
693         if value is not None:
694             if self.impl_is_multi():
695                 for idx, val in enumerate(value):
696                     if not self.impl_is_submulti():
697                         self._launch_consistency(func, self, val, None, idx,
698                                                  None, all_cons_opts,
699                                                  warnings_only)
700                     else:
701                         for slave_idx, val in enumerate(value):
702                             self._launch_consistency(func, self, val, None,
703                                                      idx, slave_idx,
704                                                      all_cons_opts,
705                                                      warnings_only)
706             else:
707                 self._launch_consistency(func, self, value, None, None,
708                                          None, all_cons_opts, warnings_only)
709         self._add_consistency(func, all_cons_opts, params)
710         self.impl_validate(self.impl_getdefault())
711
712     def _cons_not_equal(self, opts, vals, warnings_only):
713         for idx_inf, val_inf in enumerate(vals):
714             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
715                 if val_inf == val_sup is not None:
716                     if warnings_only:
717                         msg = _("same value for {0} and {1}, should be different")
718                     else:
719                         msg = _("same value for {0} and {1}, must be different")
720                     raise ValueError(msg.format(opts[idx_inf].impl_getname(),
721                                                 opts[idx_inf + idx_sup + 1].impl_getname()))
722
723     def _impl_convert_callbacks(self, descr, load=False):
724         if not load and self._callback is None:
725             self._state_callback = None
726             self._state_callback_params = None
727         elif load and self._state_callback is None:
728             self._callback = None
729             self._callback_params = None
730             del(self._state_callback)
731             del(self._state_callback_params)
732         else:
733             if load:
734                 callback = self._state_callback
735                 callback_params = self._state_callback_params
736             else:
737                 callback = self._callback
738                 callback_params = self._callback_params
739                 self._state_callback_params = None
740             if callback_params is not None:
741                 cllbck_prms = {}
742                 for key, values in callback_params.items():
743                     vls = []
744                     for value in values:
745                         if isinstance(value, tuple):
746                             if load:
747                                 value = (descr.impl_get_opt_by_path(value[0]),
748                                          value[1])
749                             else:
750                                 value = (descr.impl_get_path_by_opt(value[0]),
751                                          value[1])
752                         vls.append(value)
753                     cllbck_prms[key] = tuple(vls)
754             else:
755                 cllbck_prms = None
756
757             if load:
758                 del(self._state_callback)
759                 del(self._state_callback_params)
760                 self._callback = callback
761                 self._callback_params = cllbck_prms
762             else:
763                 self._state_callback = callback
764                 self._state_callback_params = cllbck_prms
765
766     # serialize/unserialize
767     def _impl_convert_consistencies(self, descr, load=False):
768         """during serialization process, many things have to be done.
769         one of them is the localisation of the options.
770         The paths are set once for all.
771
772         :type descr: :class:`tiramisu.option.OptionDescription`
773         :param load: `True` if we are at the init of the option description
774         :type load: bool
775         """
776         if not load and self._consistencies is None:
777             self._state_consistencies = None
778         elif load and self._state_consistencies is None:
779             self._consistencies = None
780             del(self._state_consistencies)
781         else:
782             if load:
783                 consistencies = self._state_consistencies
784             else:
785                 consistencies = self._consistencies
786             new_value = []
787             for consistency in consistencies:
788                 values = []
789                 for obj in consistency[1]:
790                     if load:
791                         values.append(descr.impl_get_opt_by_path(obj))
792                     else:
793                         values.append(descr.impl_get_path_by_opt(obj))
794                 new_value.append((consistency[0], tuple(values), consistency[2]))
795             if load:
796                 del(self._state_consistencies)
797                 self._consistencies = new_value
798             else:
799                 self._state_consistencies = new_value
800
801     def _second_level_validation(self, value, warnings_only):
802         pass
803
804
805 def validate_requires_arg(requires, name):
806     """check malformed requirements
807     and tranform dict to internal tuple
808
809     :param requires: have a look at the
810                      :meth:`tiramisu.setting.Settings.apply_requires` method to
811                      know more about
812                      the description of the requires dictionary
813     """
814     if requires is None:
815         return None, None
816     ret_requires = {}
817     config_action = {}
818
819     # start parsing all requires given by user (has dict)
820     # transforme it to a tuple
821     for require in requires:
822         if not type(require) == dict:
823             raise ValueError(_("malformed requirements type for option:"
824                                " {0}, must be a dict").format(name))
825         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
826                       'same_action')
827         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
828         if unknown_keys != frozenset():
829             raise ValueError('malformed requirements for option: {0}'
830                              ' unknown keys {1}, must only '
831                              '{2}'.format(name,
832                                           unknown_keys,
833                                           valid_keys))
834         # prepare all attributes
835         try:
836             option = require['option']
837             expected = require['expected']
838             action = require['action']
839         except KeyError:
840             raise ValueError(_("malformed requirements for option: {0}"
841                                " require must have option, expected and"
842                                " action keys").format(name))
843         if action == 'force_store_value':
844             raise ValueError(_("malformed requirements for option: {0}"
845                                " action cannot be force_store_value"
846                                ).format(name))
847         inverse = require.get('inverse', False)
848         if inverse not in [True, False]:
849             raise ValueError(_('malformed requirements for option: {0}'
850                                ' inverse must be boolean'))
851         transitive = require.get('transitive', True)
852         if transitive not in [True, False]:
853             raise ValueError(_('malformed requirements for option: {0}'
854                                ' transitive must be boolean'))
855         same_action = require.get('same_action', True)
856         if same_action not in [True, False]:
857             raise ValueError(_('malformed requirements for option: {0}'
858                                ' same_action must be boolean'))
859
860         if not isinstance(option, Option):
861             raise ValueError(_('malformed requirements '
862                                'must be an option in option {0}').format(name))
863         if option.impl_is_multi():
864             raise ValueError(_('malformed requirements option {0} '
865                                'must not be a multi for {1}').format(
866                                    option.impl_getname(), name))
867         if expected is not None:
868             try:
869                 option._validate(expected)
870             except ValueError as err:
871                 raise ValueError(_('malformed requirements second argument '
872                                    'must be valid for option {0}'
873                                    ': {1}').format(name, err))
874         if action in config_action:
875             if inverse != config_action[action]:
876                 raise ValueError(_("inconsistency in action types"
877                                    " for option: {0}"
878                                    " action: {1}").format(name, action))
879         else:
880             config_action[action] = inverse
881         if action not in ret_requires:
882             ret_requires[action] = {}
883         if option not in ret_requires[action]:
884             ret_requires[action][option] = (option, [expected], action,
885                                             inverse, transitive, same_action)
886         else:
887             ret_requires[action][option][1].append(expected)
888     # transform dict to tuple
889     ret = []
890     for opt_requires in ret_requires.values():
891         ret_action = []
892         for require in opt_requires.values():
893             ret_action.append((require[0], tuple(require[1]), require[2],
894                                require[3], require[4], require[5]))
895         ret.append(tuple(ret_action))
896     return frozenset(config_action.keys()), tuple(ret)
897
898
899 class SymLinkOption(OnlyOption):
900     #FIXME : et avec sqlalchemy ca marche vraiment ?
901     __slots__ = ('_opt', '_state_opt')
902     #not return _opt consistencies
903     #_consistencies = None
904
905     def __init__(self, name, opt):
906         self._name = name
907         if not isinstance(opt, Option):
908             raise ValueError(_('malformed symlinkoption '
909                                'must be an option '
910                                'for symlink {0}').format(name))
911         self._opt = opt
912         self._readonly = True
913         return super(Base, self).__init__()
914
915     def __getattr__(self, name):
916         if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
917             return object.__getattr__(self, name)
918         else:
919             return getattr(self._opt, name)
920
921     def _impl_getstate(self, descr):
922         super(SymLinkOption, self)._impl_getstate(descr)
923         self._state_opt = descr.impl_get_path_by_opt(self._opt)
924
925     def _impl_setstate(self, descr):
926         self._opt = descr.impl_get_opt_by_path(self._state_opt)
927         del(self._state_opt)
928         super(SymLinkOption, self)._impl_setstate(descr)
929
930     def impl_get_information(self, key, default=undefined):
931         return self._opt.impl_get_information(key, default)