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