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