7e1f78c8301215deaa3d108a614c2b1440854d37
[tiramisu.git] / tiramisu / option / baseoption.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014-2017 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 import sys
25
26 from ..i18n import _
27 from ..setting import log, undefined, debug
28 from ..autolib import carry_out_calculation
29 from ..error import (ConfigError, ValueWarning, PropertiesOptionError,
30                      display_list)
31
32 if sys.version_info[0] >= 3:  # pragma: no cover
33     from inspect import signature
34 else:
35     from inspect import getargspec
36
37 STATIC_TUPLE = tuple()
38
39 if sys.version_info[0] >= 3:  # pragma: no cover
40     xrange = range
41
42
43 submulti = 2
44 NAME_REGEXP = re.compile(r'^[a-z][a-zA-Z\d_]*$')
45 FORBIDDEN_NAMES = frozenset(['iter_all', 'iter_group', 'find', 'find_first',
46                              'make_dict', 'unwrap_from_path', 'read_only',
47                              'read_write', 'getowner', 'set_contexts'])
48 ALLOWED_CONST_LIST = ['_cons_not_equal']
49
50
51 def valid_name(name):
52     """an option's name is a str and does not start with 'impl' or 'cfgimpl'
53     and name is not a function name"""
54     if not isinstance(name, str):
55         return False
56     return re.match(NAME_REGEXP, name) is not None and \
57             name not in FORBIDDEN_NAMES and \
58             not name.startswith('impl_') and \
59             not name.startswith('cfgimpl_')
60
61
62 def validate_callback(callback, callback_params, type_, callbackoption):
63     """validate function and parameter set for callback, validation, ...
64     """
65     def _validate_option(option):
66         #validate option
67         if isinstance(option, SymLinkOption):
68             cur_opt = option._impl_getopt()
69         elif isinstance(option, Option):
70             cur_opt = option
71         else:
72             raise ValueError(_('{}_params must have an option'
73                                ' not a {} for first argument'
74                               ).format(type_, type(option)))
75         if cur_opt != callbackoption:
76             cur_opt._add_dependencies(callbackoption)
77
78     def _validate_force_permissive(force_permissive):
79         #validate force_permissive
80         if not isinstance(force_permissive, bool):
81             raise ValueError(_('{}_params must have a boolean'
82                                ' not a {} for second argument'
83                               ).format(type_, type(
84                                   force_permissive)))
85
86     def _validate_callback(callbk):
87         if isinstance(callbk, tuple):
88             if len(callbk) == 1:
89                 if callbk not in ((None,), ('index',)):
90                     raise ValueError(_('{0}_params with length of '
91                                        'tuple as 1 must only have '
92                                        'None as first value').format(type_))
93                 return
94             elif len(callbk) != 2:
95                 raise ValueError(_('{0}_params must only have 1 or 2 '
96                                    'as length').format(type_))
97             option, force_permissive = callbk
98             _validate_option(option)
99             _validate_force_permissive(force_permissive)
100
101     if not isinstance(callback, FunctionType):
102         raise ValueError(_('{0} must be a function').format(type_))
103     if callback_params is not None:
104         if not isinstance(callback_params, dict):
105             raise ValueError(_('{0}_params must be a dict').format(type_))
106         for key, callbacks in callback_params.items():
107             if key != '' and len(callbacks) != 1:
108                 raise ValueError(_("{0}_params with key {1} mustn't have "
109                                    "length different to 1").format(type_,
110                                                                    key))
111             if not isinstance(callbacks, tuple):
112                 raise ValueError(_('{0}_params must be tuple for key "{1}"'
113                                   ).format(type_, key))
114             for callbk in callbacks:
115                 _validate_callback(callbk)
116
117
118 #____________________________________________________________
119 #
120 class Base(object):
121     __slots__ = ('_name',
122                  '_informations',
123                  '_extra',
124                  '_warnings_only',
125                  '_allow_empty_list',
126                  #multi
127                  '_multi',
128                  '_unique',
129                  #value
130                  '_default',
131                  '_default_multi',
132                  #calcul
133                  '_subdyn',
134                  '_requires',
135                  '_properties',
136                  '_calc_properties',
137                  '_val_call',
138                  #
139                  '_consistencies',
140                  '_master_slaves',
141                  '_choice_values',
142                  '_choice_values_params',
143                  #other
144                  '_has_dependency',
145                  '_dependencies',
146                  '__weakref__'
147                 )
148
149     def __init__(self, name, doc, default=None, default_multi=None,
150                  requires=None, multi=False, unique=undefined, callback=None,
151                  callback_params=None, validator=None, validator_params=None,
152                  properties=None, warnings_only=False, extra=None,
153                  allow_empty_list=undefined):
154         if not valid_name(name):
155             raise ValueError(_("invalid name: {0} for option").format(name))
156         if not multi and default_multi is not None:
157             raise ValueError(_("default_multi is set whereas multi is False"
158                                " in option: {0}").format(name))
159         if multi is True:
160             is_multi = True
161             _multi = 0
162         elif multi is False:
163             is_multi = False
164             _multi = 1
165         elif multi is submulti:
166             is_multi = True
167             _multi = submulti
168         else:
169             raise ValueError(_('invalid multi value'))
170         if unique != undefined and not isinstance(unique, bool):
171             raise ValueError(_('unique must be a boolean'))
172         if not is_multi and unique is True:
173             raise ValueError(_('unique must be set only with multi value'))
174         if requires is not None:
175             calc_properties, requires = validate_requires_arg(self, is_multi,
176                                                               requires, name)
177         else:
178             calc_properties = frozenset()
179             requires = undefined
180         if properties is None:
181             properties = tuple()
182         if not isinstance(properties, tuple):
183             raise TypeError(_('invalid properties type {0} for {1},'
184                               ' must be a tuple').format(
185                                   type(properties),
186                                   name))
187         if validator is not None:
188             if multi:  # and validator_params is None:
189                 validator_params = self._build_validator_params(validator, validator_params)
190
191             validate_callback(validator, validator_params, 'validator', self)
192             if validator_params is None:
193                 val_call = (validator,)
194             else:
195                 val_call = (validator, validator_params)
196             self._val_call = (val_call, None)
197             self._set_has_dependency()
198         if calc_properties != frozenset([]) and properties is not tuple():
199             set_forbidden_properties = calc_properties & set(properties)
200             if set_forbidden_properties != frozenset():
201                 raise ValueError('conflict: properties already set in '
202                                  'requirement {0}'.format(
203                                      list(set_forbidden_properties)))
204         _setattr = object.__setattr__
205         _setattr(self, '_name', name)
206         if sys.version_info[0] < 3 and isinstance(doc, str):
207             doc = doc.decode('utf8')
208         if extra is not None:
209             _setattr(self, '_extra', extra)
210         _setattr(self, '_informations', {'doc': doc})
211         if _multi != 1:
212             _setattr(self, '_multi', _multi)
213         if warnings_only is True:
214             _setattr(self, '_warnings_only', warnings_only)
215         if calc_properties is not undefined:
216             _setattr(self, '_calc_properties', calc_properties)
217         if requires is not undefined:
218             _setattr(self, '_requires', requires)
219         if properties is not undefined:
220             _setattr(self, '_properties', properties)
221         if multi is not False and default is None:
222             default = []
223         if allow_empty_list is not undefined:
224             _setattr(self, '_allow_empty_list', allow_empty_list)
225         if unique is not undefined:
226             _setattr(self, '_unique', unique)
227         err = self.impl_validate(default, is_multi=is_multi)
228         if err:
229             raise err
230         if (is_multi and default != []) or \
231                 (not is_multi and default is not None):
232             if is_multi:
233                 default = tuple(default)
234             _setattr(self, '_default', default)
235
236         if is_multi and default_multi is not None:
237             err = self._validate(default_multi)
238             if err:
239                 raise ValueError(_("invalid default_multi value {0} "
240                                    "for option {1}: {2}").format(
241                                        str(default_multi),
242                                        self.impl_getname(), str(err)))
243             _setattr(self, '_default_multi', default_multi)
244
245         ##callback is False in optiondescription
246         if callback is not False:
247             self.impl_set_callback(callback, callback_params, _init=True)
248
249     def _build_validator_params(self, validator, validator_params):
250         if sys.version_info[0] < 3:
251             func_args = getargspec(validator)
252             defaults = func_args.defaults
253             if defaults is None:
254                 defaults = []
255             args = func_args.args[0:len(func_args.args)-len(defaults)]
256         else:  # pragma: no cover
257             func_params = signature(validator).parameters
258             args = [f.name for f in func_params.values() if f.default is f.empty]
259         if validator_params is not None:
260             kwargs = list(validator_params.keys())
261             if '' in kwargs:
262                 kwargs.remove('')
263             for kwarg in kwargs:
264                 if kwarg in args:
265                     args = args[0:args.index(kwarg)]
266             len_args = len(validator_params.get('', []))
267             if len_args != 0 and len(args) >= len_args:
268                 args = args[0:len(args)-len_args]
269         if len(args) >= 2:
270             if validator_params is not None and '' in validator_params:
271                 params = list(validator_params[''])
272                 params.append((self, False))
273                 validator_params[''] = tuple(params)
274             else:
275                 if validator_params is None:
276                     validator_params = {}
277                 validator_params[''] = ((self, False),)
278         if len(args) == 3 and args[2] not in validator_params:
279             params = list(validator_params[''])
280             params.append(('index',))
281             validator_params[''] = tuple(params)
282         return validator_params
283
284     def _set_has_dependency(self):
285         if not isinstance(self, SymLinkOption):
286             self._has_dependency = True
287
288     def impl_has_dependency(self):
289         return getattr(self, '_has_dependency', False)
290
291     def impl_set_callback(self, callback, callback_params=None, _init=False):
292         if callback is None and callback_params is not None:
293                 raise ValueError(_("params defined for a callback function but "
294                                    "no callback defined"
295                                    " yet for option {0}").format(
296                                        self.impl_getname()))
297         if not _init and self.impl_get_callback()[0] is not None:
298             raise ConfigError(_("a callback is already set for {0}, "
299                                 "cannot set another one's").format(self.impl_getname()))
300         self._validate_callback(callback, callback_params)
301         if callback is not None:
302             validate_callback(callback, callback_params, 'callback', self)
303             val = getattr(self, '_val_call', (None,))[0]
304             if callback_params is None or callback_params == {}:
305                 val_call = (callback,)
306             else:
307                 val_call = tuple([callback, callback_params])
308             self._val_call = (val, val_call)
309
310     def impl_is_optiondescription(self):
311         return self.__class__.__name__ in ['OptionDescription',
312                                            'DynOptionDescription',
313                                            'SynDynOptionDescription']
314
315     def impl_is_dynoptiondescription(self):
316         return self.__class__.__name__ in ['DynOptionDescription',
317                                            'SynDynOptionDescription']
318
319     def impl_getname(self):
320         return self._name
321
322     def impl_is_readonly(self):
323         return not isinstance(getattr(self, '_informations', dict()), dict)
324
325     def impl_getproperties(self):
326         return self._properties
327
328     def _set_readonly(self, has_extra):
329         if not self.impl_is_readonly():
330             _setattr = object.__setattr__
331             dico = self._informations
332             keys = tuple(dico.keys())
333             if len(keys) == 1:
334                 dico = dico['doc']
335             else:
336                 dico = tuple([keys, tuple(dico.values())])
337             _setattr(self, '_informations', dico)
338             if has_extra:
339                 extra = getattr(self, '_extra', None)
340                 if extra is not None:
341                     _setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())]))
342
343     def _impl_setsubdyn(self, subdyn):
344         self._subdyn = subdyn
345
346     def impl_getrequires(self):
347         return getattr(self, '_requires', STATIC_TUPLE)
348
349     def impl_get_callback(self):
350         call = getattr(self, '_val_call', (None, None))[1]
351         if call is None:
352             ret_call = (None, {})
353         elif len(call) == 1:
354             ret_call = (call[0], {})
355         else:
356             ret_call = call
357         return ret_call
358
359     # ____________________________________________________________
360     # information
361     def impl_get_information(self, key, default=undefined):
362         """retrieves one information's item
363
364         :param key: the item string (ex: "help")
365         """
366         def _is_string(infos):
367             if sys.version_info[0] >= 3:  # pragma: no cover
368                 return isinstance(infos, str)
369             else:
370                 return isinstance(infos, str) or isinstance(infos, unicode)
371
372         dico = self._informations
373         if isinstance(dico, tuple):
374             if key in dico[0]:
375                 return dico[1][dico[0].index(key)]
376         elif _is_string(dico):
377             if key == 'doc':
378                 return dico
379         elif isinstance(dico, dict):
380             if key in dico:
381                 return dico[key]
382         if default is not undefined:
383             return default
384         raise ValueError(_("information's item not found: {0}").format(
385             key))
386
387     def impl_set_information(self, key, value):
388         """updates the information's attribute
389         (which is a dictionary)
390
391         :param key: information's key (ex: "help", "doc"
392         :param value: information's value (ex: "the help string")
393         """
394         if self.impl_is_readonly():
395             raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
396                                    " read-only").format(
397                                        self.__class__.__name__,
398                                        self,
399                                        #self.impl_getname(),
400                                        key))
401         self._informations[key] = value
402
403
404 class BaseOption(Base):
405     """This abstract base class stands for attribute access
406     in options that have to be set only once, it is of course done in the
407     __setattr__ method
408     """
409     __slots__ = tuple()
410
411     def __getstate__(self):
412         raise NotImplementedError()
413
414     def __setattr__(self, name, value):
415         """set once and only once some attributes in the option,
416         like `_name`. `_name` cannot be changed one the option and
417         pushed in the :class:`tiramisu.option.OptionDescription`.
418
419         if the attribute `_readonly` is set to `True`, the option is
420         "frozen" (which has noting to do with the high level "freeze"
421         propertie or "read_only" property)
422         """
423         if name != '_option' and \
424                 not isinstance(value, tuple):
425             is_readonly = False
426             # never change _name dans _opt
427             if name == '_name':
428                 if self.impl_getname() is not None:
429                     #so _name is already set
430                     is_readonly = True
431             elif name != '_readonly':
432                 is_readonly = self.impl_is_readonly()
433             if is_readonly:
434                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
435                                        " read-only").format(
436                                            self.__class__.__name__,
437                                            self,
438                                            #self.impl_getname(),
439                                            name))
440         super(BaseOption, self).__setattr__(name, value)
441
442     def impl_getpath(self, context):
443         return context.cfgimpl_get_description().impl_get_path_by_opt(self)
444
445     def impl_has_callback(self):
446         "to know if a callback has been defined or not"
447         return self.impl_get_callback()[0] is not None
448
449     def _is_subdyn(self):
450         return getattr(self, '_subdyn', None) is not None
451
452     def _impl_valid_unicode(self, value):
453         if sys.version_info[0] >= 3:  # pragma: no cover
454             if not isinstance(value, str):
455                 return ValueError(_('invalid string'))
456         else:
457             if not isinstance(value, unicode) and not isinstance(value, str):
458                 return ValueError(_('invalid unicode or string'))
459
460     def impl_get_display_name(self, dyn_name=None):
461         name = self.impl_getdoc()
462         if name is None or name == '':
463             if dyn_name is not None:
464                 name = dyn_name
465             else:
466                 name = self.impl_getname()
467         if sys.version_info[0] < 3 and isinstance(name, unicode):
468             name = name.encode('utf8')
469         return name
470
471     def reset_cache(self, opt, obj, type_):
472         context = obj._getcontext()
473         path = self.impl_getpath(context)
474         obj._p_.delcache(path)
475         context.cfgimpl_reset_cache(only=(type_,),
476                                     opt=self,
477                                     path=path)
478
479
480 class OnlyOption(BaseOption):
481     __slots__ = tuple()
482
483
484 class Option(OnlyOption):
485     """
486     Abstract base class for configuration option's.
487
488     Reminder: an Option object is **not** a container for the value.
489     """
490     __slots__ = tuple()
491     _empty = ''
492
493     def impl_is_multi(self):
494         return getattr(self, '_multi', 1) != 1
495
496     def _add_dependencies(self, option):
497         options = set(getattr(self, '_dependencies', tuple()))
498         options.add(option)
499         self._dependencies = tuple(options)
500
501     def _launch_consistency(self, current_opt, func, option, value, context,
502                             index, submulti_index, opts, warnings_only,
503                             transitive):
504         """Launch consistency now
505
506         :param func: function name, this name should start with _cons_
507         :type func: `str`
508         :param option: option that value is changing
509         :type option: `tiramisu.option.Option`
510         :param value: new value of this option
511         :param context: Config's context, if None, check default value instead
512         :type context: `tiramisu.config.Config`
513         :param index: only for multi option, consistency should be launch for
514                       specified index
515         :type index: `int`
516         :param opts: all options concerne by this consistency
517         :type opts: `list` of `tiramisu.option.Option`
518         :param warnings_only: specific raise error for warning
519         :type warnings_only: `boolean`
520         :param transitive: propertyerror is transitive
521         :type transitive: `boolean`
522         """
523         if context is not undefined:
524             descr = context.cfgimpl_get_description()
525
526         all_cons_vals = []
527         all_cons_opts = []
528         val_consistencies = True
529         for opt in opts:
530             if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
531                     option == opt:
532                 # option is current option
533                 # we have already value, so use it
534                 all_cons_vals.append(value)
535                 all_cons_opts.append(opt)
536             else:
537                 #if context, calculate value, otherwise get default value
538                 path = None
539                 is_multi = opt.impl_is_multi() and not opt.impl_is_master_slaves()
540                 if context is not undefined:
541                     if isinstance(opt, DynSymLinkOption):
542                         path = opt.impl_getpath(context)
543                     else:
544                         path = descr.impl_get_path_by_opt(opt)
545                     if is_multi:
546                         _index = None
547                     else:
548                         _index = index
549                     opt_value = context.getattr(path, validate=False,
550                                                 index=_index,
551                                                 force_permissive=True,
552                                                 returns_raise=True)
553                     if isinstance(opt_value, Exception):
554                         if isinstance(opt_value, PropertiesOptionError):
555                             if debug:  # pragma: no cover
556                                 log.debug('propertyerror in _launch_consistency: {0}'.format(opt_value))
557                             if transitive:
558                                 opt_value.set_orig_opt(option)
559                                 return opt_value
560                             else:
561                                 opt_value = None
562                         else:  # pragma: no cover
563                             return opt_value
564                 elif index is None:
565                     opt_value = opt.impl_getdefault()
566                 else:
567                     opt_value = opt.impl_getdefault()[index]
568
569                 if self.impl_is_multi() and index is None:
570                     # only check propertyerror for master/slaves is transitive
571                     val_consistencies = False
572                 if is_multi and isinstance(opt_value, list):
573                     all_cons_vals.extend(opt_value)
574                     for len_ in xrange(len(opt_value)):
575                         all_cons_opts.append(opt)
576                 else:
577                     all_cons_vals.append(opt_value)
578                     all_cons_opts.append(opt)
579
580         if val_consistencies:
581             err = getattr(self, func)(current_opt, all_cons_opts, all_cons_vals, warnings_only)
582             if err:
583                 if warnings_only:
584                     msg = _('attention, "{0}" could be an invalid {1} for "{2}", {3}').format(
585                         value, self._display_name, current_opt.impl_get_display_name(), err)
586                     warnings.warn_explicit(ValueWarning(msg, self),
587                                            ValueWarning,
588                                            self.__class__.__name__, 0)
589                 else:
590                     return err
591
592     def impl_is_unique(self):
593         return getattr(self, '_unique', False)
594
595     def impl_get_validator(self):
596         val = getattr(self, '_val_call', (None,))[0]
597         if val is None:
598             ret_val = (None, {})
599         elif len(val) == 1:
600             ret_val = (val[0], {})
601         else:
602             ret_val = val
603         return ret_val
604
605     def impl_validate(self, value, context=undefined, validate=True,
606                       force_index=None, force_submulti_index=None,
607                       current_opt=undefined, is_multi=None,
608                       display_error=True, display_warnings=True, multi=None,
609                       setting_properties=undefined):
610         """
611         :param value: the option's value
612         :param context: Config's context
613         :type context: :class:`tiramisu.config.Config`
614         :param validate: if true enables ``self._validator`` validation
615         :type validate: boolean
616         :param force_index: if multi, value has to be a list
617                             not if force_index is not None
618         :type force_index: integer
619         :param force_submulti_index: if submulti, value has to be a list
620                                      not if force_submulti_index is not None
621         :type force_submulti_index: integer
622         """
623         if not validate:
624             return
625         if current_opt is undefined:
626             current_opt = self
627
628         if display_warnings and setting_properties is undefined and context is not undefined:
629             setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=False)
630         display_warnings = display_warnings and (setting_properties is undefined or 'warnings' in setting_properties)
631
632         def _is_not_unique(value):
633             if display_error and self.impl_is_unique() and len(set(value)) != len(value):
634                 for idx, val in enumerate(value):
635                     if val in value[idx+1:]:
636                         return ValueError(_('invalid value "{}", this value is already in "{}"').format(
637                                             val, self.impl_get_display_name()))
638
639         def calculation_validator(val, _index):
640             validator, validator_params = self.impl_get_validator()
641             if validator is not None:
642                 if validator_params != {}:
643                     validator_params_ = {}
644                     for val_param, values in validator_params.items():
645                         validator_params_[val_param] = values
646                     #inject value in calculation
647                     if '' in validator_params_:
648                         lst = list(validator_params_[''])
649                         lst.insert(0, val)
650                         validator_params_[''] = tuple(lst)
651                     else:
652                         validator_params_[''] = (val,)
653                 else:
654                     validator_params_ = {'': (val,)}
655                 # Raise ValueError if not valid
656                 value = carry_out_calculation(current_opt, context=context,
657                                               callback=validator,
658                                               callback_params=validator_params_,
659                                               index=_index,
660                                               is_validator=True)
661                 if isinstance(value, Exception):
662                     return value
663
664         def do_validation(_value, _index, submulti_index):
665             if _value is None:
666                 error = warning = None
667             else:
668                 if display_error:
669                     # option validation
670                     err = self._validate(_value, context, current_opt)
671                     if err:
672                         if debug:  # pragma: no cover
673                             log.debug('do_validation: value: {0}, index: {1}, '
674                                       'submulti_index: {2}'.format(_value, _index,
675                                                                    submulti_index),
676                                       exc_info=True)
677                         err_msg = '{0}'.format(err)
678                         if err_msg:
679                             msg = _('"{0}" is an invalid {1} for "{2}", {3}'
680                                     '').format(_value, self._display_name,
681                                                self.impl_get_display_name(), err_msg)
682                         else:
683                             msg = _('"{0}" is an invalid {1} for "{2}"'
684                                     '').format(_value, self._display_name,
685                                                self.impl_get_display_name())
686                         return ValueError(msg)
687                 error = None
688                 is_warnings_only = getattr(self, '_warnings_only', False)
689                 if ((display_error and not is_warnings_only) or
690                         (display_warnings and is_warnings_only)):
691                     error = calculation_validator(_value, _index)
692                     if not error:
693                         error = self._second_level_validation(_value, is_warnings_only)
694                     if error:
695                         if debug:  # pragma: no cover
696                             log.debug(_('do_validation for {0}: error in value').format(
697                                 self.impl_getname()), exc_info=True)
698                         if is_warnings_only:
699                             msg = _('attention, "{0}" could be an invalid {1} for "{2}", {3}').format(
700                                 _value, self._display_name, self.impl_get_display_name(), error)
701                             warnings.warn_explicit(ValueWarning(msg, self),
702                                                    ValueWarning,
703                                                    self.__class__.__name__, 0)
704                             error = None
705             if error is None:
706                 # if context launch consistency validation
707                 #if context is not undefined:
708                 ret = self._valid_consistency(current_opt, _value, context,
709                                               _index, submulti_index, display_warnings,
710                                               display_error)
711                 if isinstance(ret, ValueError):
712                     error = ret
713                 elif ret:
714                     return ret
715             if error:
716                 err_msg = '{0}'.format(error)
717                 if err_msg:
718                     msg = _('"{0}" is an invalid {1} for "{2}", {3}'
719                             '').format(_value, self._display_name,
720                                        self.impl_get_display_name(), err_msg)
721                 else:
722                     msg = _('"{0}" is an invalid {1} for "{2}"'
723                             '').format(_value, self._display_name,
724                                        self.impl_get_display_name())
725                 return ValueError(msg)
726
727         if is_multi is None:
728             is_multi = self.impl_is_multi()
729
730         if not is_multi:
731             return do_validation(value, None, None)
732         elif force_index is not None:
733             if self.impl_is_submulti() and force_submulti_index is None:
734                 err = _is_not_unique(value)
735                 if err:
736                     return err
737                 if not isinstance(value, list):
738                     return ValueError(_('invalid value "{0}" for "{1}" which'
739                                         ' must be a list').format(
740                                            value, self.impl_get_display_name()))
741                 for idx, val in enumerate(value):
742                     if isinstance(val, list):  # pragma: no cover
743                         return ValueError(_('invalid value "{}" for "{}" '
744                                             'which must not be a list').format(val,
745                                                                               self.impl_get_display_name()))
746                     err = do_validation(val, force_index, idx)
747                     if err:
748                         return err
749             else:
750                 if multi is not None and self.impl_is_unique() and value in multi:
751                     if not self.impl_is_submulti() and len(multi) - 1 >= force_index:
752                         lst = list(multi)
753                         lst.pop(force_index)
754                     else:
755                         lst = multi
756                     if value in lst:
757                         return ValueError(_('invalid value "{}", this value is already'
758                                             ' in "{}"').format(value,
759                                                                self.impl_get_display_name()))
760                 return do_validation(value, force_index, force_submulti_index)
761         elif not isinstance(value, list):
762             return ValueError(_('invalid value "{0}" for "{1}" which '
763                                 'must be a list').format(value,
764                                                          self.impl_getname()))
765         elif self.impl_is_submulti() and force_submulti_index is None:
766             for idx, val in enumerate(value):
767                 err = _is_not_unique(val)
768                 if err:
769                     return err
770                 if not isinstance(val, list):
771                     return ValueError(_('invalid value "{0}" for "{1}" '
772                                         'which must be a list of list'
773                                         '').format(val,
774                                                    self.impl_getname()))
775                 for slave_idx, slave_val in enumerate(val):
776                     err = do_validation(slave_val, idx, slave_idx)
777                     if err:
778                         return err
779         else:
780             err = _is_not_unique(value)
781             if err:
782                 return err
783             for idx, val in enumerate(value):
784                 err = do_validation(val, idx, force_submulti_index)
785                 if err:
786                     return err
787             return self._valid_consistency(current_opt, None, context,
788                                            None, None, display_warnings, display_error)
789
790     def impl_is_dynsymlinkoption(self):
791         return False
792
793     def impl_is_master_slaves(self, type_='both'):
794         """FIXME
795         """
796         master_slaves = self.impl_get_master_slaves()
797         if master_slaves is not None:
798             if type_ in ('both', 'master') and \
799                     master_slaves.is_master(self):
800                 return True
801             if type_ in ('both', 'slave') and \
802                     not master_slaves.is_master(self):
803                 return True
804         return False
805
806     def impl_get_master_slaves(self):
807         return getattr(self, '_master_slaves', None)
808
809     def impl_getdoc(self):
810         "accesses the Option's doc"
811         return self.impl_get_information('doc')
812
813     def _valid_consistencies(self, other_opts, init=True, func=None):
814         if self._is_subdyn():
815             dynod = self._subdyn
816         else:
817             dynod = None
818         if self.impl_is_submulti():
819             raise ConfigError(_('cannot add consistency with submulti option'))
820         is_multi = self.impl_is_multi()
821         for opt in other_opts:
822             if opt.impl_is_submulti():
823                 raise ConfigError(_('cannot add consistency with submulti option'))
824             if not isinstance(opt, Option):
825                 raise ConfigError(_('consistency must be set with an option'))
826             if opt._is_subdyn():
827                 if dynod is None:
828                     raise ConfigError(_('almost one option in consistency is '
829                                         'in a dynoptiondescription but not all'))
830                 if dynod != opt._subdyn:
831                     raise ConfigError(_('option in consistency must be in same'
832                                         ' dynoptiondescription'))
833                 dynod = opt._subdyn
834             elif dynod is not None:
835                 raise ConfigError(_('almost one option in consistency is in a '
836                                     'dynoptiondescription but not all'))
837             if self is opt:
838                 raise ConfigError(_('cannot add consistency with itself'))
839             if is_multi != opt.impl_is_multi():
840                 raise ConfigError(_('every options in consistency must be '
841                                     'multi or none'))
842             if init:
843                 # FIXME
844                 if func != 'not_equal':
845                     opt._set_has_dependency()
846
847     def impl_add_consistency(self, func, *other_opts, **params):
848         """Add consistency means that value will be validate with other_opts
849         option's values.
850
851         :param func: function's name
852         :type func: `str`
853         :param other_opts: options used to validate value
854         :type other_opts: `list` of `tiramisu.option.Option`
855         :param params: extra params (warnings_only and transitive are allowed)
856         """
857         if self.impl_is_readonly(): 
858             raise AttributeError(_("'{0}' ({1}) cannot add consistency, option is"
859                                    " read-only").format(
860                                        self.__class__.__name__,
861                                        self.impl_getname()))
862         self._valid_consistencies(other_opts, func=func)
863         func = '_cons_{0}'.format(func)
864         if func not in dir(self):
865             raise ConfigError(_('consistency {0} not available for this option').format(func))
866         all_cons_opts = tuple([self] + list(other_opts))
867         unknown_params = set(params.keys()) - set(['warnings_only', 'transitive'])
868         if unknown_params != set():
869             raise ValueError(_('unknow parameter {0} in consistency').format(unknown_params))
870         self._add_consistency(func, all_cons_opts, params)
871         #validate default value when add consistency
872         err = self.impl_validate(self.impl_getdefault())
873         if err:
874             self._del_consistency()
875             raise err
876         if func in ALLOWED_CONST_LIST:
877             for opt in all_cons_opts:
878                 if getattr(opt, '_unique', undefined) == undefined:
879                     opt._unique = True
880         if func != '_cons_not_equal':
881             #consistency could generate warnings or errors
882             self._set_has_dependency()
883
884     def _valid_consistency(self, option, value, context, index, submulti_idx,
885                            display_warnings, display_error):
886         if context is not undefined:
887             descr = context.cfgimpl_get_description()
888             if descr._cache_consistencies is None:
889                 return
890             #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
891             if isinstance(option, DynSymLinkOption):
892                 consistencies = descr._cache_consistencies.get(option._impl_getopt())
893             else:
894                 consistencies = descr._cache_consistencies.get(option)
895         else:
896             consistencies = option._get_consistencies()
897         if consistencies is not None:
898             for func, all_cons_opts, params in consistencies:
899                 warnings_only = params.get('warnings_only', False)
900                 if (warnings_only and display_warnings) or (not warnings_only and display_error):
901                     transitive = params.get('transitive', True)
902                     #all_cons_opts[0] is the option where func is set
903                     if isinstance(option, DynSymLinkOption):
904                         subpath = '.'.join(option._dyn.split('.')[:-1])
905                         namelen = len(option._impl_getopt().impl_getname())
906                         suffix = option.impl_getname()[namelen:]
907                         opts = []
908                         for opt in all_cons_opts:
909                             name = opt.impl_getname() + suffix
910                             path = subpath + '.' + name
911                             opts.append(opt._impl_to_dyn(name, path))
912                     else:
913                         opts = all_cons_opts
914                     err = opts[0]._launch_consistency(self, func, option, value,
915                                                       context, index, submulti_idx,
916                                                       opts, warnings_only,
917                                                       transitive)
918                     if err:
919                         return err
920
921     def _cons_not_equal(self, current_opt, opts, vals, warnings_only):
922         equal = set()
923         is_current = False
924         for idx_inf, val_inf in enumerate(vals):
925             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
926                 if val_inf == val_sup is not None:
927                     for opt_ in [opts[idx_inf], opts[idx_inf + idx_sup + 1]]:
928                         if opt_ == current_opt:
929                             is_current = True
930                         else:
931                             equal.add(opt_)
932         if equal:
933             if debug:  # pragma: no cover
934                 log.debug(_('_cons_not_equal: {} are not different').format(display_list(list(equal))))
935             if is_current:
936                 if warnings_only:
937                     msg = _('should be different from the value of {}')
938                 else:
939                     msg = _('must be different from the value of {}')
940             else:
941                 if warnings_only:
942                     msg = _('value for {} should be different')
943                 else:
944                     msg = _('value for {} must be different')
945             equal_name = []
946             for opt in equal:
947                 equal_name.append(opt.impl_get_display_name())
948             return ValueError(msg.format(display_list(list(equal_name))))
949
950     def _second_level_validation(self, value, warnings_only):
951         pass
952
953     def _impl_to_dyn(self, name, path):
954         return DynSymLinkOption(name, self, dyn=path)
955
956     def impl_getdefault_multi(self):
957         "accessing the default value for a multi"
958         return getattr(self, '_default_multi', None)
959
960     def _validate_callback(self, callback, callback_params):
961         """callback_params:
962         * None
963         * {'': ((option, permissive),), 'ip': ((None,), (option, permissive))
964         """
965         if callback is None:
966             return
967         default_multi = self.impl_getdefault_multi()
968         is_multi = self.impl_is_multi()
969         default = self.impl_getdefault()
970         if (not is_multi and (default is not None or default_multi is not None)) or \
971                 (is_multi and (default != [] or default_multi is not None)):
972             raise ValueError(_("default value not allowed if option: {0} "
973                              "is calculated").format(self.impl_getname()))
974
975     def impl_getdefault(self):
976         "accessing the default value"
977         is_multi = self.impl_is_multi()
978         default = getattr(self, '_default', undefined)
979         if default is undefined:
980             if is_multi:
981                 default = []
982             else:
983                 default = None
984         else:
985             if is_multi:
986                 default = list(default)
987         return default
988
989     def _get_extra(self, key):
990         extra = self._extra
991         if isinstance(extra, tuple):
992             return extra[1][extra[0].index(key)]
993         else:
994             return extra[key]
995
996     def impl_is_submulti(self):
997         return getattr(self, '_multi', 1) == 2
998
999     def impl_allow_empty_list(self):
1000         return getattr(self, '_allow_empty_list', undefined)
1001
1002     #____________________________________________________________
1003     # consistency
1004     def _add_consistency(self, func, all_cons_opts, params):
1005         cons = (func, all_cons_opts, params)
1006         consistencies = getattr(self, '_consistencies', None)
1007         if consistencies is None:
1008             self._consistencies = [cons]
1009         else:
1010             consistencies.append(cons)
1011
1012     def _del_consistency(self):
1013         self._consistencies.pop(-1)
1014
1015     def _get_consistencies(self):
1016         return getattr(self, '_consistencies', STATIC_TUPLE)
1017
1018     def _has_consistencies(self):
1019         return hasattr(self, '_consistencies')
1020
1021
1022 def validate_requires_arg(new_option, multi, requires, name):
1023     """check malformed requirements
1024     and tranform dict to internal tuple
1025
1026     :param requires: have a look at the
1027                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1028                      know more about
1029                      the description of the requires dictionary
1030     """
1031     def set_dependency(option):
1032         if not getattr(option, '_dependencies', None):
1033             options = set()
1034         else:
1035             options = set(option._dependencies)
1036         options.add(new_option)
1037         option._dependencies = tuple(options)
1038
1039     def get_option(require):
1040         option = require['option']
1041         if not isinstance(option, Option):
1042             raise ValueError(_('malformed requirements '
1043                                'must be an option in option {0}').format(name))
1044         if not multi and option.impl_is_multi():
1045             raise ValueError(_('malformed requirements '
1046                                'multi option must not set '
1047                                'as requires of non multi option {0}').format(name))
1048         set_dependency(option)
1049         return option
1050
1051     def _set_expected(action, inverse, transitive, same_action, option, expected, operator):
1052         if inverse not in ret_requires[action]:
1053             ret_requires[action][inverse] = ([(option, [expected])], action, inverse, transitive, same_action, operator)
1054         else:
1055             for exp in ret_requires[action][inverse][0]:
1056                 if exp[0] == option:
1057                     exp[1].append(expected)
1058                     break
1059             else:
1060                 ret_requires[action][inverse][0].append((option, [expected]))
1061
1062     def set_expected(require, ret_requires):
1063         expected = require['expected']
1064         inverse = get_inverse(require)
1065         transitive = get_transitive(require)
1066         same_action = get_sameaction(require)
1067         operator = get_operator(require)
1068         if isinstance(expected, list):
1069             for exp in expected:
1070                 if set(exp.keys()) != {'option', 'value'}:
1071                     raise ValueError(_('malformed requirements expected must have '
1072                                        'option and value for option {0}').format(name))
1073                 option = exp['option']
1074                 set_dependency(option)
1075                 if option is not None:
1076                     err = option._validate(exp['value'])
1077                     if err:
1078                         raise ValueError(_('malformed requirements expected value '
1079                                            'must be valid for option {0}'
1080                                            ': {1}').format(name, err))
1081                 _set_expected(action, inverse, transitive, same_action, option, exp['value'], operator)
1082         else:
1083             option = get_option(require)
1084             if expected is not None:
1085                 err = option._validate(expected)
1086                 if err:
1087                     raise ValueError(_('malformed requirements expected value '
1088                                        'must be valid for option {0}'
1089                                        ': {1}').format(name, err))
1090             _set_expected(action, inverse, transitive, same_action, option, expected, operator)
1091
1092     def get_action(require):
1093         action = require['action']
1094         if action == 'force_store_value':
1095             raise ValueError(_("malformed requirements for option: {0}"
1096                                " action cannot be force_store_value"
1097                                ).format(name))
1098         return action
1099
1100     def get_inverse(require):
1101         inverse = require.get('inverse', False)
1102         if inverse not in [True, False]:
1103             raise ValueError(_('malformed requirements for option: {0}'
1104                                ' inverse must be boolean'))
1105         return inverse
1106
1107     def get_transitive(require):
1108         transitive = require.get('transitive', True)
1109         if transitive not in [True, False]:
1110             raise ValueError(_('malformed requirements for option: {0}'
1111                                ' transitive must be boolean'))
1112         return transitive
1113
1114     def get_sameaction(require):
1115         same_action = require.get('same_action', True)
1116         if same_action not in [True, False]:
1117             raise ValueError(_('malformed requirements for option: {0}'
1118                                ' same_action must be boolean'))
1119         return same_action
1120
1121     def get_operator(require):
1122         operator = require.get('operator', 'or')
1123         if operator not in ['and', 'or']:
1124             raise ValueError(_('malformed requirements for option: "{0}"'
1125                                ' operator must be "or" or "and"').format(operator))
1126         return operator
1127
1128
1129     ret_requires = {}
1130     config_action = set()
1131
1132     # start parsing all requires given by user (has dict)
1133     # transforme it to a tuple
1134     for require in requires:
1135         if not isinstance(require, dict):
1136             raise ValueError(_("malformed requirements type for option:"
1137                                " {0}, must be a dict").format(name))
1138         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1139                       'same_action', 'operator')
1140         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1141         if unknown_keys != frozenset():
1142             raise ValueError(_('malformed requirements for option: {0}'
1143                              ' unknown keys {1}, must only '
1144                              '{2}').format(name,
1145                                            unknown_keys,
1146                                            valid_keys))
1147         # prepare all attributes
1148         if not ('expected' in require and isinstance(require['expected'], list)) and \
1149                 not ('option' in require and 'expected' in require) or \
1150                 'action' not in require:
1151             raise ValueError(_("malformed requirements for option: {0}"
1152                                " require must have option, expected and"
1153                                " action keys").format(name))
1154         action = get_action(require)
1155         config_action.add(action)
1156         if action not in ret_requires:
1157             ret_requires[action] = {}
1158         set_expected(require, ret_requires)
1159
1160     # transform dict to tuple
1161     ret = []
1162     for requires in ret_requires.values():
1163         ret_action = []
1164         for require in requires.values():
1165             ret_action.append((tuple(require[0]), require[1],
1166                                require[2], require[3], require[4], require[5]))
1167         ret.append(tuple(ret_action))
1168     return frozenset(config_action), tuple(ret)
1169
1170
1171 class SymLinkOption(OnlyOption):
1172
1173     def __init__(self, name, opt):
1174         if not isinstance(opt, Option):
1175             raise ValueError(_('malformed symlinkoption '
1176                                'must be an option '
1177                                'for symlink {0}').format(name))
1178         _setattr = object.__setattr__
1179         _setattr(self, '_name', name)
1180         _setattr(self, '_opt', opt)
1181         opt._set_has_dependency()
1182
1183     def __getattr__(self, name, context=undefined):
1184         return getattr(self._impl_getopt(), name)
1185
1186     def _impl_getopt(self):
1187         return self._opt
1188
1189     def impl_get_information(self, key, default=undefined):
1190         return self._impl_getopt().impl_get_information(key, default)
1191
1192     def impl_is_readonly(self):
1193         return True
1194
1195     def impl_getproperties(self):
1196         return self._impl_getopt()._properties
1197
1198     def impl_get_callback(self):
1199         return self._impl_getopt().impl_get_callback()
1200
1201     def impl_has_callback(self):
1202         "to know if a callback has been defined or not"
1203         return self._impl_getopt().impl_has_callback()
1204
1205     def impl_is_multi(self):
1206         return self._impl_getopt().impl_is_multi()
1207
1208     def _is_subdyn(self):
1209         return getattr(self._impl_getopt(), '_subdyn', None) is not None
1210
1211     def _get_consistencies(self):
1212         return ()
1213
1214     def _has_consistencies(self):
1215         return False
1216
1217
1218 class DynSymLinkOption(object):
1219     __slots__ = ('_dyn', '_opt', '_name')
1220
1221     def __init__(self, name, opt, dyn):
1222         self._name = name
1223         self._dyn = dyn
1224         self._opt = opt
1225
1226     def __getattr__(self, name, context=undefined):
1227         return getattr(self._impl_getopt(), name)
1228
1229     def impl_getname(self):
1230         return self._name
1231
1232     def impl_get_display_name(self):
1233         return self._impl_getopt().impl_get_display_name(dyn_name=self.impl_getname())
1234
1235     def _impl_getopt(self):
1236         return self._opt
1237
1238     def impl_getsuffix(self):
1239         return self._dyn.split('.')[-1][len(self._impl_getopt().impl_getname()):]
1240
1241     def impl_getpath(self, context):
1242         path = self._impl_getopt().impl_getpath(context)
1243         base_path = '.'.join(path.split('.')[:-2])
1244         if self.impl_is_master_slaves() and base_path is not '':
1245             base_path = base_path + self.impl_getsuffix()
1246         if base_path == '':
1247             return self._dyn
1248         else:
1249             return base_path + '.' + self._dyn
1250
1251     def impl_validate(self, value, context=undefined, validate=True,
1252                       force_index=None, force_submulti_index=None, is_multi=None,
1253                       display_error=True, display_warnings=True, multi=None,
1254                       setting_properties=undefined):
1255         return self._impl_getopt().impl_validate(value, context, validate,
1256                                                  force_index,
1257                                                  force_submulti_index,
1258                                                  current_opt=self,
1259                                                  is_multi=is_multi,
1260                                                  display_error=display_error,
1261                                                  display_warnings=display_warnings,
1262                                                  multi=multi,
1263                                                  setting_properties=setting_properties)
1264
1265     def impl_is_dynsymlinkoption(self):
1266         return True