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