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