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