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