2bb9328f65148d335b6fa66de9cba0688ed4e98e
[tiramisu.git] / tiramisu / option.py
1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 import re
24 import sys
25 from copy import copy, deepcopy
26 from types import FunctionType
27 from IPy import IP
28 import warnings
29
30 from tiramisu.error import ConfigError, ConflictError, ValueWarning
31 from tiramisu.setting import groups, multitypes
32 from tiramisu.i18n import _
33 from tiramisu.autolib import carry_out_calculation
34
35 name_regexp = re.compile(r'^\d+')
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     try:
44         name = str(name)
45     except:
46         return False
47     if re.match(name_regexp, name) is None and not name.startswith('_') \
48             and name not in forbidden_names \
49             and not name.startswith('impl_') \
50             and not name.startswith('cfgimpl_'):
51         return True
52     else:
53         return False
54 #____________________________________________________________
55 #
56
57
58 class BaseOption(object):
59     """This abstract base class stands for attribute access
60     in options that have to be set only once, it is of course done in the
61     __setattr__ method
62     """
63     __slots__ = ('_name', '_requires', '_properties', '_readonly',
64                  '_calc_properties', '_impl_informations',
65                  '_state_readonly', '_state_requires', '_stated')
66
67     def __init__(self, name, doc, requires, properties):
68         if not valid_name(name):
69             raise ValueError(_("invalid name: {0} for option").format(name))
70         self._name = name
71         self._impl_informations = {}
72         self.impl_set_information('doc', doc)
73         self._calc_properties, self._requires = validate_requires_arg(
74             requires, self._name)
75         if properties is None:
76             properties = tuple()
77         if not isinstance(properties, tuple):
78             raise TypeError(_('invalid properties type {0} for {1},'
79                             ' must be a tuple').format(
80                                 type(properties),
81                                 self._name))
82         if self._calc_properties is not None and properties is not tuple():
83             set_forbidden_properties = set(properties) & self._calc_properties
84             if set_forbidden_properties != frozenset():
85                 raise ValueError('conflict: properties already set in '
86                                  'requirement {0}'.format(
87                                      list(set_forbidden_properties)))
88         self._properties = properties  # 'hidden', 'disabled'...
89
90     def __setattr__(self, name, value):
91         """set once and only once some attributes in the option,
92         like `_name`. `_name` cannot be changed one the option and
93         pushed in the :class:`tiramisu.option.OptionDescription`.
94
95         if the attribute `_readonly` is set to `True`, the option is
96         "frozen" (which has noting to do with the high level "freeze"
97         propertie or "read_only" property)
98         """
99         if not name.startswith('_state') and not name.startswith('_cache'):
100             is_readonly = False
101             # never change _name
102             if name == '_name':
103                 try:
104                     self._name
105                     #so _name is already set
106                     is_readonly = True
107                 except:
108                     pass
109             elif name != '_readonly':
110                 try:
111                     if self._readonly is True:
112                         is_readonly = True
113                 except AttributeError:
114                     self._readonly = False
115             if is_readonly:
116                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
117                                        " read-only").format(
118                                            self.__class__.__name__,
119                                            self._name,
120                                            name))
121         object.__setattr__(self, name, value)
122
123     # information
124     def impl_set_information(self, key, value):
125         """updates the information's attribute
126         (which is a dictionary)
127
128         :param key: information's key (ex: "help", "doc"
129         :param value: information's value (ex: "the help string")
130         """
131         self._impl_informations[key] = value
132
133     def impl_get_information(self, key, default=None):
134         """retrieves one information's item
135
136         :param key: the item string (ex: "help")
137         """
138         if key in self._impl_informations:
139             return self._impl_informations[key]
140         elif default is not None:
141             return default
142         else:
143             raise ValueError(_("information's item not found: {0}").format(
144                 key))
145
146     def _impl_convert_requires(self, descr, load=False):
147         """export of the requires during the serialization process
148
149         :type descr: :class:`tiramisu.option.OptionDescription`
150         :param load: `True` if we are at the init of the option description
151         :type load: bool
152         """
153         if not load and self._requires is None:
154             self._state_requires = None
155         elif load and self._state_requires is None:
156             self._requires = None
157             del(self._state_requires)
158         else:
159             if load:
160                 _requires = self._state_requires
161             else:
162                 _requires = self._requires
163             new_value = []
164             for requires in _requires:
165                 new_requires = []
166                 for require in requires:
167                     if load:
168                         new_require = [descr.impl_get_opt_by_path(require[0])]
169                     else:
170                         new_require = [descr.impl_get_path_by_opt(require[0])]
171                     new_require.extend(require[1:])
172                     new_requires.append(tuple(new_require))
173                 new_value.append(tuple(new_requires))
174             if load:
175                 del(self._state_requires)
176                 self._requires = new_value
177             else:
178                 self._state_requires = new_value
179
180     # serialize
181     def _impl_getstate(self, descr):
182         """the under the hood stuff that need to be done
183         before the serialization.
184
185         :param descr: the parent :class:`tiramisu.option.OptionDescription`
186         """
187         self._stated = True
188         for func in dir(self):
189             if func.startswith('_impl_convert_'):
190                 getattr(self, func)(descr)
191         self._state_readonly = self._readonly
192
193     def __getstate__(self, stated=True):
194         """special method to enable the serialization with pickle
195         Usualy, a `__getstate__` method does'nt need any parameter,
196         but somme under the hood stuff need to be done before this action
197
198         :parameter stated: if stated is `True`, the serialization protocol
199                            can be performed, not ready yet otherwise
200         :parameter type: bool
201         """
202         try:
203             self._stated
204         except AttributeError:
205             raise SystemError(_('cannot serialize Option, '
206                                 'only in OptionDescription'))
207         slots = set()
208         for subclass in self.__class__.__mro__:
209             if subclass is not object:
210                 slots.update(subclass.__slots__)
211         slots -= frozenset(['_cache_paths', '_cache_consistencies',
212                             '__weakref__'])
213         states = {}
214         for slot in slots:
215             # remove variable if save variable converted
216             # in _state_xxxx variable
217             if '_state' + slot not in slots:
218                 if slot.startswith('_state'):
219                     # should exists
220                     states[slot] = getattr(self, slot)
221                     # remove _state_xxx variable
222                     self.__delattr__(slot)
223                 else:
224                     try:
225                         states[slot] = getattr(self, slot)
226                     except AttributeError:
227                         pass
228         if not stated:
229             del(states['_stated'])
230         return states
231
232     # unserialize
233     def _impl_setstate(self, descr):
234         """the under the hood stuff that need to be done
235         before the serialization.
236
237         :type descr: :class:`tiramisu.option.OptionDescription`
238         """
239         for func in dir(self):
240             if func.startswith('_impl_convert_'):
241                 getattr(self, func)(descr, load=True)
242         try:
243             self._readonly = self._state_readonly
244             del(self._state_readonly)
245             del(self._stated)
246         except AttributeError:
247             pass
248
249     def __setstate__(self, state):
250         """special method that enables us to serialize (pickle)
251
252         Usualy, a `__setstate__` method does'nt need any parameter,
253         but somme under the hood stuff need to be done before this action
254
255         :parameter state: a dict is passed to the loads, it is the attributes
256                           of the options object
257         :type state: dict
258         """
259         for key, value in state.items():
260             setattr(self, key, value)
261
262
263 class Option(BaseOption):
264     """
265     Abstract base class for configuration option's.
266
267     Reminder: an Option object is **not** a container for the value.
268     """
269     __slots__ = ('_multi', '_validator', '_default_multi', '_default',
270                  '_state_callback', '_callback', '_multitype',
271                  '_consistencies', '_warnings_only', '_master_slaves',
272                  '_state_consistencies', '__weakref__')
273     _empty = ''
274
275     def __init__(self, name, doc, default=None, default_multi=None,
276                  requires=None, multi=False, callback=None,
277                  callback_params=None, validator=None, validator_params=None,
278                  properties=None, warnings_only=False):
279         """
280         :param name: the option's name
281         :param doc: the option's description
282         :param default: specifies the default value of the option,
283                         for a multi : ['bla', 'bla', 'bla']
284         :param default_multi: 'bla' (used in case of a reset to default only at
285                         a given index)
286         :param requires: is a list of names of options located anywhere
287                          in the configuration.
288         :param multi: if true, the option's value is a list
289         :param callback: the name of a function. If set, the function's output
290                          is responsible of the option's value
291         :param callback_params: the callback's parameter
292         :param validator: the name of a function which stands for a custom
293                           validation of the value
294         :param validator_params: the validator's parameters
295         :param properties: tuple of default properties
296         :param warnings_only: _validator and _consistencies don't raise if True
297                              Values()._warning contain message
298
299         """
300         super(Option, self).__init__(name, doc, requires, properties)
301         self._multi = multi
302         if validator is not None:
303             validate_callback(validator, validator_params, 'validator')
304             self._validator = (validator, validator_params)
305         else:
306             self._validator = None
307         if not self._multi and default_multi is not None:
308             raise ValueError(_("a default_multi is set whereas multi is False"
309                              " in option: {0}").format(name))
310         if default_multi is not None:
311             try:
312                 self._validate(default_multi)
313             except ValueError as err:
314                 raise ValueError(_("invalid default_multi value {0} "
315                                    "for option {1}: {2}").format(
316                                        str(default_multi), name, err))
317         if callback is not None and (default is not None or
318                                      default_multi is not None):
319             raise ValueError(_("default value not allowed if option: {0} "
320                              "is calculated").format(name))
321         if callback is None and callback_params is not None:
322             raise ValueError(_("params defined for a callback function but "
323                              "no callback defined"
324                              " yet for option {0}").format(name))
325         if callback is not None:
326             validate_callback(callback, callback_params, 'callback')
327             self._callback = (callback, callback_params)
328         else:
329             self._callback = None
330         if self._multi:
331             if default is None:
332                 default = []
333             self._multitype = multitypes.default
334             self._default_multi = default_multi
335         self._warnings_only = warnings_only
336         self.impl_validate(default)
337         self._default = default
338         self._consistencies = None
339
340     def _launch_consistency(self, func, option, value, context, index,
341                             all_cons_opts):
342         """Launch consistency now
343
344         :param func: function name, this name should start with _cons_
345         :type func: `str`
346         :param option: option that value is changing
347         :type option: `tiramisu.option.Option`
348         :param value: new value of this option
349         :param context: Config's context, if None, check default value instead
350         :type context: `tiramisu.config.Config`
351         :param index: only for multi option, consistency should be launch for
352                       specified index
353         :type index: `int`
354         :param all_cons_opts: all options concerne by this consistency
355         :type all_cons_opts: `list` of `tiramisu.option.Option`
356         """
357         if context is not None:
358             descr = context.cfgimpl_get_description()
359         #option is also in all_cons_opts
360         if option not in all_cons_opts:
361             raise ConfigError(_('option not in all_cons_opts'))
362
363         all_cons_vals = []
364         for opt in all_cons_opts:
365             #get value
366             if option == opt:
367                 opt_value = value
368             else:
369                 #if context, calculate value, otherwise get default value
370                 if context is not None:
371                     opt_value = context._getattr(
372                         descr.impl_get_path_by_opt(opt), validate=False)
373                 else:
374                     opt_value = opt.impl_getdefault()
375
376             #append value
377             if not self.impl_is_multi() or option == opt:
378                 all_cons_vals.append(opt_value)
379             else:
380                 #value is not already set, could be higher index
381                 try:
382                     all_cons_vals.append(opt_value[index])
383                 except IndexError:
384                     #so return if no value
385                     return
386         getattr(self, func)(all_cons_opts, all_cons_vals)
387
388     def impl_validate(self, value, context=None, validate=True,
389                       force_index=None):
390         """
391         :param value: the option's value
392         :param context: Config's context
393         :type context: :class:`tiramisu.config.Config`
394         :param validate: if true enables ``self._validator`` validation
395         :type validate: boolean
396         :param force_no_multi: if multi, value has to be a list
397                                not if force_no_multi is True
398         :type force_no_multi: boolean
399         """
400         if not validate:
401             return
402
403         def val_validator(val):
404             if self._validator is not None:
405                 if self._validator[1] is not None:
406                     validator_params = deepcopy(self._validator[1])
407                     if '' in validator_params:
408                         lst = list(validator_params[''])
409                         lst.insert(0, val)
410                         validator_params[''] = tuple(lst)
411                     else:
412                         validator_params[''] = (val,)
413                 else:
414                     validator_params = {'': (val,)}
415                 # Raise ValueError if not valid
416                 carry_out_calculation(self._name, config=context,
417                                       callback=self._validator[0],
418                                       callback_params=validator_params)
419
420         def do_validation(_value, _index=None):
421             if _value is None:
422                 return
423             # option validation
424             self._validate(_value)
425             try:
426                 # valid with self._validator
427                 val_validator(_value)
428                 # if not context launch consistency validation
429                 if context is not None:
430                     descr._valid_consistency(self, _value, context, _index)
431                 self._second_level_validation(_value)
432             except ValueError as err:
433                 msg = _("invalid value {0} for option {1}: {2}").format(
434                     _value, self._name, err)
435                 if self._warnings_only:
436                     warnings.warn_explicit(ValueWarning(msg, self),
437                                            ValueWarning,
438                                            self.__class__.__name__, 0)
439                 else:
440                     raise ValueError(msg)
441
442         # generic calculation
443         if context is not None:
444             descr = context.cfgimpl_get_description()
445
446         if not self._multi or force_index is not None:
447             do_validation(value, force_index)
448         else:
449             if not isinstance(value, list):
450                 raise ValueError(_("which must be a list").format(value,
451                                                                   self._name))
452             for index, val in enumerate(value):
453                 do_validation(val, index)
454
455     def impl_getdefault(self, default_multi=False):
456         "accessing the default value"
457         if not default_multi or not self.impl_is_multi():
458             return self._default
459         else:
460             return self.getdefault_multi()
461
462     def impl_getdefault_multi(self):
463         "accessing the default value for a multi"
464         return self._default_multi
465
466     def impl_get_multitype(self):
467         return self._multitype
468
469     def impl_get_master_slaves(self):
470         return self._master_slaves
471
472     def impl_is_empty_by_default(self):
473         "no default value has been set yet"
474         if ((not self.impl_is_multi() and self._default is None) or
475                 (self.impl_is_multi() and (self._default == []
476                                            or None in self._default))):
477             return True
478         return False
479
480     def impl_getdoc(self):
481         "accesses the Option's doc"
482         return self.impl_get_information('doc')
483
484     def impl_has_callback(self):
485         "to know if a callback has been defined or not"
486         if self._callback is None:
487             return False
488         else:
489             return True
490
491     def impl_getkey(self, value):
492         return value
493
494     def impl_is_multi(self):
495         return self._multi
496
497     def impl_add_consistency(self, func, *other_opts):
498         """Add consistency means that value will be validate with other_opts
499         option's values.
500
501         :param func: function's name
502         :type func: `str`
503         :param other_opts: options used to validate value
504         :type other_opts: `list` of `tiramisu.option.Option`
505         """
506         if self._consistencies is None:
507             self._consistencies = []
508         for opt in other_opts:
509             if not isinstance(opt, Option):
510                 raise ConfigError(_('consistency should be set with an option'))
511             if self is opt:
512                 raise ConfigError(_('cannot add consistency with itself'))
513             if self.impl_is_multi() != opt.impl_is_multi():
514                 raise ConfigError(_('every options in consistency should be '
515                                     'multi or none'))
516         func = '_cons_{0}'.format(func)
517         all_cons_opts = tuple([self] + list(other_opts))
518         value = self.impl_getdefault()
519         if value is not None:
520             if self.impl_is_multi():
521                 for idx, val in enumerate(value):
522                     self._launch_consistency(func, self, val, None,
523                                              idx, all_cons_opts)
524             else:
525                 self._launch_consistency(func, self, value, None,
526                                          None, all_cons_opts)
527         self._consistencies.append((func, all_cons_opts))
528         self.impl_validate(self.impl_getdefault())
529
530     def _cons_not_equal(self, opts, vals):
531         for idx_inf, val_inf in enumerate(vals):
532             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
533                 if val_inf == val_sup is not None:
534                     raise ValueError(_("same value for {0} and {1}").format(
535                         opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
536
537     def _impl_convert_callbacks(self, descr, load=False):
538         if not load and self._callback is None:
539             self._state_callback = None
540         elif load and self._state_callback is None:
541             self._callback = None
542             del(self._state_callback)
543         else:
544             if load:
545                 callback, callback_params = self._state_callback
546             else:
547                 callback, callback_params = self._callback
548             if callback_params is not None:
549                 cllbck_prms = {}
550                 for key, values in callback_params.items():
551                     vls = []
552                     for value in values:
553                         if isinstance(value, tuple):
554                             if load:
555                                 value = (descr.impl_get_opt_by_path(value[0]),
556                                          value[1])
557                             else:
558                                 value = (descr.impl_get_path_by_opt(value[0]),
559                                          value[1])
560                         vls.append(value)
561                     cllbck_prms[key] = tuple(vls)
562             else:
563                 cllbck_prms = None
564
565             if load:
566                 del(self._state_callback)
567                 self._callback = (callback, cllbck_prms)
568             else:
569                 self._state_callback = (callback, cllbck_prms)
570
571     # serialize/unserialize
572     def _impl_convert_consistencies(self, descr, load=False):
573         """during serialization process, many things have to be done.
574         one of them is the localisation of the options.
575         The paths are set once for all.
576
577         :type descr: :class:`tiramisu.option.OptionDescription`
578         :param load: `True` if we are at the init of the option description
579         :type load: bool
580         """
581         if not load and self._consistencies is None:
582             self._state_consistencies = None
583         elif load and self._state_consistencies is None:
584             self._consistencies = None
585             del(self._state_consistencies)
586         else:
587             if load:
588                 consistencies = self._state_consistencies
589             else:
590                 consistencies = self._consistencies
591             if isinstance(consistencies, list):
592                 new_value = []
593                 for consistency in consistencies:
594                     values = []
595                     for obj in consistency[1]:
596                         if load:
597                             values.append(descr.impl_get_opt_by_path(obj))
598                         else:
599                             values.append(descr.impl_get_path_by_opt(obj))
600                     new_value.append((consistency[0], tuple(values)))
601
602             else:
603                 new_value = {}
604                 for key, _consistencies in consistencies.items():
605                     new_value[key] = []
606                     for key_cons, _cons in _consistencies:
607                         _list_cons = []
608                         for _con in _cons:
609                             if load:
610                                 _list_cons.append(
611                                     descr.impl_get_opt_by_path(_con))
612                             else:
613                                 _list_cons.append(
614                                     descr.impl_get_path_by_opt(_con))
615                         new_value[key].append((key_cons, tuple(_list_cons)))
616             if load:
617                 del(self._state_consistencies)
618                 self._consistencies = new_value
619             else:
620                 self._state_consistencies = new_value
621
622     def _second_level_validation(self, value):
623         pass
624
625
626 class ChoiceOption(Option):
627     """represents a choice out of several objects.
628
629     The option can also have the value ``None``
630     """
631
632     __slots__ = ('_values', '_open_values')
633     _opt_type = 'string'
634
635     def __init__(self, name, doc, values, default=None, default_multi=None,
636                  requires=None, multi=False, callback=None,
637                  callback_params=None, open_values=False, validator=None,
638                  validator_params=None, properties=None, warnings_only=False):
639         """
640         :param values: is a list of values the option can possibly take
641         """
642         if not isinstance(values, tuple):
643             raise TypeError(_('values must be a tuple for {0}').format(name))
644         self._values = values
645         if open_values not in (True, False):
646             raise TypeError(_('open_values must be a boolean for '
647                             '{0}').format(name))
648         self._open_values = open_values
649         super(ChoiceOption, self).__init__(name, doc, default=default,
650                                            default_multi=default_multi,
651                                            callback=callback,
652                                            callback_params=callback_params,
653                                            requires=requires,
654                                            multi=multi,
655                                            validator=validator,
656                                            validator_params=validator_params,
657                                            properties=properties,
658                                            warnings_only=warnings_only)
659
660     def impl_get_values(self):
661         return self._values
662
663     def impl_is_openvalues(self):
664         return self._open_values
665
666     def _validate(self, value):
667         if not self._open_values and not value in self._values:
668             raise ValueError(_('value {0} is not permitted, '
669                                'only {1} is allowed'
670                                '').format(value, self._values))
671
672
673 class BoolOption(Option):
674     "represents a choice between ``True`` and ``False``"
675     __slots__ = tuple()
676     _opt_type = 'bool'
677
678     def _validate(self, value):
679         if not isinstance(value, bool):
680             raise ValueError(_('value must be a boolean'))
681
682
683 class IntOption(Option):
684     "represents a choice of an integer"
685     __slots__ = tuple()
686     _opt_type = 'int'
687
688     def _validate(self, value):
689         if not isinstance(value, int):
690             raise ValueError(_('value must be an integer'))
691
692
693 class FloatOption(Option):
694     "represents a choice of a floating point number"
695     __slots__ = tuple()
696     _opt_type = 'float'
697
698     def _validate(self, value):
699         if not isinstance(value, float):
700             raise ValueError(_('value must be a float'))
701
702
703 class StrOption(Option):
704     "represents the choice of a string"
705     __slots__ = tuple()
706     _opt_type = 'string'
707
708     def _validate(self, value):
709         if not isinstance(value, str):
710             raise ValueError(_('value must be a string, not '
711                                '{0}').format(type(value)))
712
713
714 if sys.version_info[0] >= 3:
715     #UnicodeOption is same has StrOption in python 3+
716     class UnicodeOption(StrOption):
717         __slots__ = tuple()
718         pass
719 else:
720     class UnicodeOption(Option):
721         "represents the choice of a unicode string"
722         __slots__ = tuple()
723         _opt_type = 'unicode'
724         _empty = u''
725
726         def _validate(self, value):
727             if not isinstance(value, unicode):
728                 raise ValueError(_('value must be an unicode'))
729
730
731 class SymLinkOption(BaseOption):
732     __slots__ = ('_name', '_opt', '_state_opt')
733     _opt_type = 'symlink'
734     #not return _opt consistencies
735     _consistencies = None
736
737     def __init__(self, name, opt):
738         self._name = name
739         if not isinstance(opt, Option):
740             raise ValueError(_('malformed symlinkoption '
741                                'must be an option '
742                                'for symlink {0}').format(name))
743         self._opt = opt
744         self._readonly = True
745
746     def __getattr__(self, name):
747         if name in ('_name', '_opt', '_opt_type', '_readonly'):
748             return object.__getattr__(self, name)
749         else:
750             return getattr(self._opt, name)
751
752     def _impl_getstate(self, descr):
753         super(SymLinkOption, self)._impl_getstate(descr)
754         self._state_opt = descr.impl_get_path_by_opt(self._opt)
755
756     def _impl_setstate(self, descr):
757         self._opt = descr.impl_get_opt_by_path(self._state_opt)
758         del(self._state_opt)
759         super(SymLinkOption, self)._impl_setstate(descr)
760
761
762 class IPOption(Option):
763     "represents the choice of an ip"
764     __slots__ = ('_private_only', '_allow_reserved')
765     _opt_type = 'ip'
766
767     def __init__(self, name, doc, default=None, default_multi=None,
768                  requires=None, multi=False, callback=None,
769                  callback_params=None, validator=None, validator_params=None,
770                  properties=None, private_only=False, allow_reserved=False,
771                  warnings_only=False):
772         self._private_only = private_only
773         self._allow_reserved = allow_reserved
774         super(IPOption, self).__init__(name, doc, default=default,
775                                        default_multi=default_multi,
776                                        callback=callback,
777                                        callback_params=callback_params,
778                                        requires=requires,
779                                        multi=multi,
780                                        validator=validator,
781                                        validator_params=validator_params,
782                                        properties=properties,
783                                        warnings_only=warnings_only)
784
785     def _validate(self, value):
786         try:
787             IP('{0}/32'.format(value))
788         except ValueError:
789             raise ValueError(_('invalid IP {0}').format(self._name))
790
791     def _second_level_validation(self, value):
792         ip = IP('{0}/32'.format(value))
793         if not self._allow_reserved and ip.iptype() == 'RESERVED':
794             raise ValueError(_("IP mustn't not be in reserved class"))
795         if self._private_only and not ip.iptype() == 'PRIVATE':
796             raise ValueError(_("IP must be in private class"))
797
798
799 class PortOption(Option):
800     """represents the choice of a port
801     The port numbers are divided into three ranges:
802     the well-known ports,
803     the registered ports,
804     and the dynamic or private ports.
805     You can actived this three range.
806     Port number 0 is reserved and can't be used.
807     see: http://en.wikipedia.org/wiki/Port_numbers
808     """
809     __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
810     _opt_type = 'port'
811
812     def __init__(self, name, doc, default=None, default_multi=None,
813                  requires=None, multi=False, callback=None,
814                  callback_params=None, validator=None, validator_params=None,
815                  properties=None, allow_range=False, allow_zero=False,
816                  allow_wellknown=True, allow_registred=True,
817                  allow_private=False, warnings_only=False):
818         self._allow_range = allow_range
819         self._min_value = None
820         self._max_value = None
821         ports_min = [0, 1, 1024, 49152]
822         ports_max = [0, 1023, 49151, 65535]
823         is_finally = False
824         for index, allowed in enumerate([allow_zero,
825                                          allow_wellknown,
826                                          allow_registred,
827                                          allow_private]):
828             if self._min_value is None:
829                 if allowed:
830                     self._min_value = ports_min[index]
831             elif not allowed:
832                 is_finally = True
833             elif allowed and is_finally:
834                 raise ValueError(_('inconsistency in allowed range'))
835             if allowed:
836                 self._max_value = ports_max[index]
837
838         if self._max_value is None:
839             raise ValueError(_('max value is empty'))
840
841         super(PortOption, self).__init__(name, doc, default=default,
842                                          default_multi=default_multi,
843                                          callback=callback,
844                                          callback_params=callback_params,
845                                          requires=requires,
846                                          multi=multi,
847                                          validator=validator,
848                                          validator_params=validator_params,
849                                          properties=properties,
850                                          warnings_only=warnings_only)
851
852     def _validate(self, value):
853         if self._allow_range and ":" in str(value):
854             value = str(value).split(':')
855             if len(value) != 2:
856                 raise ValueError('range must have two values only')
857             if not value[0] < value[1]:
858                 raise ValueError('first port in range must be'
859                                  ' smaller than the second one')
860         else:
861             value = [value]
862
863         for val in value:
864             if not self._min_value <= int(val) <= self._max_value:
865                 raise ValueError('port must be an between {0} and {1}'
866                                  ''.format(self._min_value, self._max_value))
867
868
869 class NetworkOption(Option):
870     "represents the choice of a network"
871     __slots__ = tuple()
872     _opt_type = 'network'
873
874     def _validate(self, value):
875         try:
876             IP(value)
877         except ValueError:
878             raise ValueError(_('invalid network address {0}').format(self._name))
879
880     def _second_level_validation(self, value):
881         ip = IP(value)
882         if ip.iptype() == 'RESERVED':
883             raise ValueError(_("network shall not be in reserved class"))
884
885
886 class NetmaskOption(Option):
887     "represents the choice of a netmask"
888     __slots__ = tuple()
889     _opt_type = 'netmask'
890
891     def _validate(self, value):
892         try:
893             IP('0.0.0.0/{0}'.format(value))
894         except ValueError:
895             raise ValueError(_('invalid netmask address {0}').format(self._name))
896
897     def _cons_network_netmask(self, opts, vals):
898         #opts must be (netmask, network) options
899         if None in vals:
900             return
901         self.__cons_netmask(opts, vals[0], vals[1], False)
902
903     def _cons_ip_netmask(self, opts, vals):
904         #opts must be (netmask, ip) options
905         if None in vals:
906             return
907         self.__cons_netmask(opts, vals[0], vals[1], True)
908
909     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
910         if len(opts) != 2:
911             raise ConfigError(_('invalid len for opts'))
912         msg = None
913         try:
914             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
915                     make_net=make_net)
916             #if cidr == 32, ip same has network
917             if ip.prefixlen() != 32:
918                 try:
919                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
920                         make_net=not make_net)
921                 except ValueError:
922                     if not make_net:
923                         msg = _("invalid network {0} ({1}) "
924                                 "with netmask {2} ({3}),"
925                                 " this network is an IP")
926                 else:
927                     if make_net:
928                         msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
929                                 " this IP is a network")
930
931         except ValueError:
932             if make_net:
933                 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
934             else:
935                 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
936         if msg is not None:
937             raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
938                                         val_netmask, self._name))
939
940
941 class BroadcastOption(Option):
942     __slots__ = tuple()
943     _opt_type = 'broadcast'
944
945     def _validate(self, value):
946         try:
947             IP('{0}/32'.format(value))
948         except ValueError:
949             raise ValueError(_('invalid broadcast address {0}').format(self._name))
950
951     def _cons_broadcast(self, opts, vals):
952         if len(vals) != 3:
953             raise ConfigError(_('invalid len for vals'))
954         if None in vals:
955             return
956         broadcast, network, netmask = vals
957         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
958             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
959                                '({3}) and netmask {4} ({5})').format(
960                                    broadcast, opts[0]._name, network,
961                                    opts[1]._name, netmask, opts[2]._name))
962
963
964 class DomainnameOption(Option):
965     """represents the choice of a domain name
966     netbios: for MS domain
967     hostname: to identify the device
968     domainname:
969     fqdn: with tld, not supported yet
970     """
971     __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
972     _opt_type = 'domainname'
973
974     def __init__(self, name, doc, default=None, default_multi=None,
975                  requires=None, multi=False, callback=None,
976                  callback_params=None, validator=None, validator_params=None,
977                  properties=None, allow_ip=False, type_='domainname',
978                  warnings_only=False, allow_without_dot=False):
979         if type_ not in ['netbios', 'hostname', 'domainname']:
980             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
981         self._type = type_
982         if allow_ip not in [True, False]:
983             raise ValueError(_('allow_ip must be a boolean'))
984         if allow_without_dot not in [True, False]:
985             raise ValueError(_('allow_without_dot must be a boolean'))
986         self._allow_ip = allow_ip
987         self._allow_without_dot = allow_without_dot
988         end = ''
989         extrachar = ''
990         if self._type == 'netbios':
991             length = 14
992         elif self._type == 'hostname':
993             length = 62
994         elif self._type == 'domainname':
995             length = 62
996             extrachar = '\.'
997             end = '+[a-z]*'
998         self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-]{{,{0}}}{1}){2}$'
999                                      ''.format(length, extrachar, end))
1000         super(DomainnameOption, self).__init__(name, doc, default=default,
1001                                                default_multi=default_multi,
1002                                                callback=callback,
1003                                                callback_params=callback_params,
1004                                                requires=requires,
1005                                                multi=multi,
1006                                                validator=validator,
1007                                                validator_params=validator_params,
1008                                                properties=properties,
1009                                                warnings_only=warnings_only)
1010
1011     def _validate(self, value):
1012         if self._allow_ip is True:
1013             try:
1014                 IP('{0}/32'.format(value))
1015                 return
1016             except ValueError:
1017                 pass
1018         if self._type == 'domainname' and not self._allow_without_dot and \
1019                 '.' not in value:
1020             raise ValueError(_("invalid domainname for {0}, must have dot"
1021                                "").format(self._name))
1022             if len(value) > 255:
1023                 raise ValueError(_("invalid domainname's length for"
1024                                    " {0} (max 255)").format(self._name))
1025         if len(value) < 2:
1026             raise ValueError(_("invalid domainname's length for {0} (min 2)"
1027                                "").format(self._name))
1028         if not self._domain_re.search(value):
1029             raise ValueError(_('invalid domainname: {0}'.format(self._name)))
1030
1031
1032 class EmailOption(DomainnameOption):
1033     __slots__ = tuple()
1034     _opt_type = 'email'
1035     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1036
1037     def __init__(self, *args, **kwargs):
1038         kwargs['type_'] = 'domainname'
1039         kwargs['allow_ip'] = False
1040         kwargs['allow_without_dot'] = False
1041         super(EmailOption, self).__init__(*args, **kwargs)
1042
1043     def _validate(self, value):
1044         splitted = value.split('@', 1)
1045         try:
1046             username, domain = splitted
1047         except ValueError:
1048             raise ValueError(_('invalid email address, should contains one @ '
1049                                'for {0}').format(self._name))
1050         if not self.username_re.search(username):
1051             raise ValueError(_('invalid username in email address for {0}').format(self._name))
1052         super(EmailOption, self)._validate(domain)
1053
1054
1055 class URLOption(DomainnameOption):
1056     __slots__ = tuple()
1057     _opt_type = 'url'
1058     proto_re = re.compile(r'(http|https)://')
1059     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1060
1061     def __init__(self, *args, **kwargs):
1062         kwargs['type_'] = 'domainname'
1063         kwargs['allow_ip'] = False
1064         kwargs['allow_without_dot'] = False
1065         super(URLOption, self).__init__(*args, **kwargs)
1066
1067     def _validate(self, value):
1068         match = self.proto_re.search(value)
1069         if not match:
1070             raise ValueError(_('invalid url, should start with http:// or '
1071                                'https:// for {0}').format(self._name))
1072         value = value[len(match.group(0)):]
1073         # get domain/files
1074         splitted = value.split('/', 1)
1075         try:
1076             domain, files = splitted
1077         except ValueError:
1078             domain = value
1079             files = None
1080         # if port in domain
1081         splitted = domain.split(':', 1)
1082         try:
1083             domain, port = splitted
1084
1085         except ValueError:
1086             domain = splitted[0]
1087             port = 0
1088         if not 0 <= int(port) <= 65535:
1089             raise ValueError(_('port must be an between 0 and 65536'))
1090         # validate domainname
1091         super(URLOption, self)._validate(domain)
1092         # validate file
1093         if files is not None and files != '' and not self.path_re.search(files):
1094             raise ValueError(_('invalid url, should endswith with filename for'
1095                                ' {0}').format(self._name))
1096
1097
1098 class FileOption(Option):
1099     __slots__ = tuple()
1100     _opt_type = 'file'
1101     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1102
1103     def _validate(self, value):
1104         match = self.path_re.search(value)
1105         if not match:
1106             raise ValueError(_('invalid filename for {0}').format(self._name))
1107
1108
1109 class OptionDescription(BaseOption):
1110     """Config's schema (organisation, group) and container of Options
1111     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1112     """
1113     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
1114                  '_state_group_type', '_properties', '_children',
1115                  '_cache_consistencies', '_calc_properties', '__weakref__',
1116                  '_readonly', '_impl_informations', '_state_requires',
1117                  '_stated', '_state_readonly')
1118     _opt_type = 'optiondescription'
1119
1120     def __init__(self, name, doc, children, requires=None, properties=None):
1121         """
1122         :param children: a list of options (including optiondescriptions)
1123
1124         """
1125         super(OptionDescription, self).__init__(name, doc, requires, properties)
1126         child_names = [child._name for child in children]
1127         #better performance like this
1128         valid_child = copy(child_names)
1129         valid_child.sort()
1130         old = None
1131         for child in valid_child:
1132             if child == old:
1133                 raise ConflictError(_('duplicate option name: '
1134                                       '{0}').format(child))
1135             old = child
1136         self._children = (tuple(child_names), tuple(children))
1137         self._cache_paths = None
1138         self._cache_consistencies = None
1139         # the group_type is useful for filtering OptionDescriptions in a config
1140         self._group_type = groups.default
1141
1142     def impl_getdoc(self):
1143         return self.impl_get_information('doc')
1144
1145     def __getattr__(self, name):
1146         if name in self.__slots__:
1147             return object.__getattribute__(self, name)
1148         try:
1149             return self._children[1][self._children[0].index(name)]
1150         except ValueError:
1151             raise AttributeError(_('unknown Option {0} '
1152                                    'in OptionDescription {1}'
1153                                    '').format(name, self._name))
1154
1155     def impl_getkey(self, config):
1156         return tuple([child.impl_getkey(getattr(config, child._name))
1157                       for child in self.impl_getchildren()])
1158
1159     def impl_getpaths(self, include_groups=False, _currpath=None):
1160         """returns a list of all paths in self, recursively
1161            _currpath should not be provided (helps with recursion)
1162         """
1163         if _currpath is None:
1164             _currpath = []
1165         paths = []
1166         for option in self.impl_getchildren():
1167             attr = option._name
1168             if isinstance(option, OptionDescription):
1169                 if include_groups:
1170                     paths.append('.'.join(_currpath + [attr]))
1171                 paths += option.impl_getpaths(include_groups=include_groups,
1172                                               _currpath=_currpath + [attr])
1173             else:
1174                 paths.append('.'.join(_currpath + [attr]))
1175         return paths
1176
1177     def impl_getchildren(self):
1178         return self._children[1]
1179
1180     def impl_build_cache(self,
1181                          cache_path=None,
1182                          cache_option=None,
1183                          _currpath=None,
1184                          _consistencies=None,
1185                          force_no_consistencies=False):
1186         if _currpath is None and self._cache_paths is not None:
1187             # cache already set
1188             return
1189         if _currpath is None:
1190             save = True
1191             _currpath = []
1192             if not force_no_consistencies:
1193                 _consistencies = {}
1194         else:
1195             save = False
1196         if cache_path is None:
1197             cache_path = []
1198             cache_option = []
1199         for option in self.impl_getchildren():
1200             attr = option._name
1201             if option in cache_option:
1202                 raise ConflictError(_('duplicate option: {0}').format(option))
1203
1204             cache_option.append(option)
1205             if not force_no_consistencies:
1206                 option._readonly = True
1207             cache_path.append(str('.'.join(_currpath + [attr])))
1208             if not isinstance(option, OptionDescription):
1209                 if not force_no_consistencies and \
1210                         option._consistencies is not None:
1211                     for consistency in option._consistencies:
1212                         func, all_cons_opts = consistency
1213                         for opt in all_cons_opts:
1214                             _consistencies.setdefault(opt,
1215                                                       []).append((func,
1216                                                                   all_cons_opts))
1217             else:
1218                 _currpath.append(attr)
1219                 option.impl_build_cache(cache_path,
1220                                         cache_option,
1221                                         _currpath,
1222                                         _consistencies,
1223                                         force_no_consistencies)
1224                 _currpath.pop()
1225         if save:
1226             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1227             if not force_no_consistencies:
1228                 if _consistencies != {}:
1229                     self._cache_consistencies = {}
1230                     for opt, cons in _consistencies.items():
1231                         if opt not in cache_option:
1232                             raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
1233                         self._cache_consistencies[opt] = tuple(cons)
1234                 self._readonly = True
1235
1236     def impl_get_opt_by_path(self, path):
1237         try:
1238             return self._cache_paths[0][self._cache_paths[1].index(path)]
1239         except ValueError:
1240             raise AttributeError(_('no option for path {0}').format(path))
1241
1242     def impl_get_path_by_opt(self, opt):
1243         try:
1244             return self._cache_paths[1][self._cache_paths[0].index(opt)]
1245         except ValueError:
1246             raise AttributeError(_('no option {0} found').format(opt))
1247
1248     # ____________________________________________________________
1249     def impl_set_group_type(self, group_type):
1250         """sets a given group object to an OptionDescription
1251
1252         :param group_type: an instance of `GroupType` or `MasterGroupType`
1253                               that lives in `setting.groups`
1254         """
1255         if self._group_type != groups.default:
1256             raise TypeError(_('cannot change group_type if already set '
1257                             '(old {0}, new {1})').format(self._group_type,
1258                                                          group_type))
1259         if isinstance(group_type, groups.GroupType):
1260             self._group_type = group_type
1261             if isinstance(group_type, groups.MasterGroupType):
1262                 #if master (same name has group) is set
1263                 identical_master_child_name = False
1264                 #for collect all slaves
1265                 slaves = []
1266                 master = None
1267                 for child in self.impl_getchildren():
1268                     if isinstance(child, OptionDescription):
1269                         raise ValueError(_("master group {0} shall not have "
1270                                          "a subgroup").format(self._name))
1271                     if isinstance(child, SymLinkOption):
1272                         raise ValueError(_("master group {0} shall not have "
1273                                          "a symlinkoption").format(self._name))
1274                     if not child.impl_is_multi():
1275                         raise ValueError(_("not allowed option {0} "
1276                                          "in group {1}"
1277                                          ": this option is not a multi"
1278                                          "").format(child._name, self._name))
1279                     if child._name == self._name:
1280                         identical_master_child_name = True
1281                         child._multitype = multitypes.master
1282                         master = child
1283                     else:
1284                         slaves.append(child)
1285                 if master is None:
1286                     raise ValueError(_('master group with wrong'
1287                                        ' master name for {0}'
1288                                        ).format(self._name))
1289                 master._master_slaves = tuple(slaves)
1290                 for child in self.impl_getchildren():
1291                     if child != master:
1292                         child._master_slaves = master
1293                         child._multitype = multitypes.slave
1294                 if not identical_master_child_name:
1295                     raise ValueError(_("no child has same nom has master group"
1296                                        " for: {0}").format(self._name))
1297         else:
1298             raise ValueError(_('group_type: {0}'
1299                                ' not allowed').format(group_type))
1300
1301     def impl_get_group_type(self):
1302         return self._group_type
1303
1304     def _valid_consistency(self, option, value, context, index):
1305         if self._cache_consistencies is None:
1306             return True
1307         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1308         consistencies = self._cache_consistencies.get(option)
1309         if consistencies is not None:
1310             for func, all_cons_opts in consistencies:
1311                 #all_cons_opts[0] is the option where func is set
1312                 ret = all_cons_opts[0]._launch_consistency(func, option,
1313                                                            value,
1314                                                            context, index,
1315                                                            all_cons_opts)
1316                 if ret is False:
1317                     return False
1318         return True
1319
1320     def _impl_getstate(self, descr=None):
1321         """enables us to export into a dict
1322         :param descr: parent :class:`tiramisu.option.OptionDescription`
1323         """
1324         if descr is None:
1325             self.impl_build_cache()
1326             descr = self
1327         super(OptionDescription, self)._impl_getstate(descr)
1328         self._state_group_type = str(self._group_type)
1329         for option in self.impl_getchildren():
1330             option._impl_getstate(descr)
1331
1332     def __getstate__(self):
1333         """special method to enable the serialization with pickle
1334         """
1335         stated = True
1336         try:
1337             # the `_state` attribute is a flag that which tells us if
1338             # the serialization can be performed
1339             self._stated
1340         except AttributeError:
1341             # if cannot delete, _impl_getstate never launch
1342             # launch it recursivement
1343             # _stated prevent __getstate__ launch more than one time
1344             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1345             self._impl_getstate()
1346             stated = False
1347         return super(OptionDescription, self).__getstate__(stated)
1348
1349     def _impl_setstate(self, descr=None):
1350         """enables us to import from a dict
1351         :param descr: parent :class:`tiramisu.option.OptionDescription`
1352         """
1353         if descr is None:
1354             self._cache_paths = None
1355             self._cache_consistencies = None
1356             self.impl_build_cache(force_no_consistencies=True)
1357             descr = self
1358         self._group_type = getattr(groups, self._state_group_type)
1359         del(self._state_group_type)
1360         super(OptionDescription, self)._impl_setstate(descr)
1361         for option in self.impl_getchildren():
1362             option._impl_setstate(descr)
1363
1364     def __setstate__(self, state):
1365         super(OptionDescription, self).__setstate__(state)
1366         try:
1367             self._stated
1368         except AttributeError:
1369             self._impl_setstate()
1370
1371
1372 def validate_requires_arg(requires, name):
1373     """check malformed requirements
1374     and tranform dict to internal tuple
1375
1376     :param requires: have a look at the
1377                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1378                      know more about
1379                      the description of the requires dictionary
1380     """
1381     if requires is None:
1382         return None, None
1383     ret_requires = {}
1384     config_action = {}
1385
1386     # start parsing all requires given by user (has dict)
1387     # transforme it to a tuple
1388     for require in requires:
1389         if not type(require) == dict:
1390             raise ValueError(_("malformed requirements type for option:"
1391                                " {0}, must be a dict").format(name))
1392         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1393                       'same_action')
1394         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1395         if unknown_keys != frozenset():
1396             raise ValueError('malformed requirements for option: {0}'
1397                              ' unknown keys {1}, must only '
1398                              '{2}'.format(name,
1399                                           unknown_keys,
1400                                           valid_keys))
1401         # prepare all attributes
1402         try:
1403             option = require['option']
1404             expected = require['expected']
1405             action = require['action']
1406         except KeyError:
1407             raise ValueError(_("malformed requirements for option: {0}"
1408                                " require must have option, expected and"
1409                                " action keys").format(name))
1410         inverse = require.get('inverse', False)
1411         if inverse not in [True, False]:
1412             raise ValueError(_('malformed requirements for option: {0}'
1413                                ' inverse must be boolean'))
1414         transitive = require.get('transitive', True)
1415         if transitive not in [True, False]:
1416             raise ValueError(_('malformed requirements for option: {0}'
1417                                ' transitive must be boolean'))
1418         same_action = require.get('same_action', True)
1419         if same_action not in [True, False]:
1420             raise ValueError(_('malformed requirements for option: {0}'
1421                                ' same_action must be boolean'))
1422
1423         if not isinstance(option, Option):
1424             raise ValueError(_('malformed requirements '
1425                                'must be an option in option {0}').format(name))
1426         if option.impl_is_multi():
1427             raise ValueError(_('malformed requirements option {0} '
1428                                'should not be a multi').format(name))
1429         if expected is not None:
1430             try:
1431                 option._validate(expected)
1432             except ValueError as err:
1433                 raise ValueError(_('malformed requirements second argument '
1434                                    'must be valid for option {0}'
1435                                    ': {1}').format(name, err))
1436         if action in config_action:
1437             if inverse != config_action[action]:
1438                 raise ValueError(_("inconsistency in action types"
1439                                    " for option: {0}"
1440                                    " action: {1}").format(name, action))
1441         else:
1442             config_action[action] = inverse
1443         if action not in ret_requires:
1444             ret_requires[action] = {}
1445         if option not in ret_requires[action]:
1446             ret_requires[action][option] = (option, [expected], action,
1447                                             inverse, transitive, same_action)
1448         else:
1449             ret_requires[action][option][1].append(expected)
1450     # transform dict to tuple
1451     ret = []
1452     for opt_requires in ret_requires.values():
1453         ret_action = []
1454         for require in opt_requires.values():
1455             ret_action.append((require[0], tuple(require[1]), require[2],
1456                                require[3], require[4], require[5]))
1457         ret.append(tuple(ret_action))
1458     return frozenset(config_action.keys()), tuple(ret)
1459
1460
1461 def validate_callback(callback, callback_params, type_):
1462     if type(callback) != FunctionType:
1463         raise ValueError(_('{0} should be a function').format(type_))
1464     if callback_params is not None:
1465         if not isinstance(callback_params, dict):
1466             raise ValueError(_('{0}_params should be a dict').format(type_))
1467         for key, callbacks in callback_params.items():
1468             if key != '' and len(callbacks) != 1:
1469                 raise ValueError(_('{0}_params with key {1} should not have '
1470                                    'length different to 1').format(type_,
1471                                                                    key))
1472             if not isinstance(callbacks, tuple):
1473                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1474                                    ).format(type_, key))
1475             for callbk in callbacks:
1476                 if isinstance(callbk, tuple):
1477                     option, force_permissive = callbk
1478                     if type_ == 'validator' and not force_permissive:
1479                         raise ValueError(_('validator not support tuple'))
1480                     if not isinstance(option, Option) and not \
1481                             isinstance(option, SymLinkOption):
1482                         raise ValueError(_('{0}_params should have an option '
1483                                            'not a {0} for first argument'
1484                                            ).format(type_, type(option)))
1485                     if force_permissive not in [True, False]:
1486                         raise ValueError(_('{0}_params should have a boolean'
1487                                            ' not a {0} for second argument'
1488                                            ).format(type_, type(
1489                                                force_permissive)))