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