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