imprecise error message
[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             try:
425                 self._validate(_value)
426             except ValueError as err:
427                 raise ValueError(_('invalid value for option {0}: {1}'
428                                    '').format(self._name, err))
429             try:
430                 # valid with self._validator
431                 val_validator(_value)
432                 # if not context launch consistency validation
433                 if context is not None:
434                     descr._valid_consistency(self, _value, context, _index)
435                 self._second_level_validation(_value)
436             except ValueError as err:
437                 msg = _("invalid value for option {0}: {1}").format(
438                     self._name, err)
439                 if self._warnings_only:
440                     warnings.warn_explicit(ValueWarning(msg, self),
441                                            ValueWarning,
442                                            self.__class__.__name__, 0)
443                 else:
444                     raise ValueError(msg)
445
446         # generic calculation
447         if context is not None:
448             descr = context.cfgimpl_get_description()
449
450         if not self._multi or force_index is not None:
451             do_validation(value, force_index)
452         else:
453             if not isinstance(value, list):
454                 raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name))
455             for index, val in enumerate(value):
456                 do_validation(val, index)
457
458     def impl_getdefault(self, default_multi=False):
459         "accessing the default value"
460         if not default_multi or not self.impl_is_multi():
461             return self._default
462         else:
463             return self.getdefault_multi()
464
465     def impl_getdefault_multi(self):
466         "accessing the default value for a multi"
467         return self._default_multi
468
469     def impl_get_multitype(self):
470         return self._multitype
471
472     def impl_get_master_slaves(self):
473         return self._master_slaves
474
475     def impl_is_empty_by_default(self):
476         "no default value has been set yet"
477         if ((not self.impl_is_multi() and self._default is None) or
478                 (self.impl_is_multi() and (self._default == []
479                                            or None in self._default))):
480             return True
481         return False
482
483     def impl_getdoc(self):
484         "accesses the Option's doc"
485         return self.impl_get_information('doc')
486
487     def impl_has_callback(self):
488         "to know if a callback has been defined or not"
489         if self._callback is None:
490             return False
491         else:
492             return True
493
494     def impl_getkey(self, value):
495         return value
496
497     def impl_is_multi(self):
498         return self._multi
499
500     def impl_add_consistency(self, func, *other_opts):
501         """Add consistency means that value will be validate with other_opts
502         option's values.
503
504         :param func: function's name
505         :type func: `str`
506         :param other_opts: options used to validate value
507         :type other_opts: `list` of `tiramisu.option.Option`
508         """
509         if self._consistencies is None:
510             self._consistencies = []
511         for opt in other_opts:
512             if not isinstance(opt, Option):
513                 raise ConfigError(_('consistency should be set with an option'))
514             if self is opt:
515                 raise ConfigError(_('cannot add consistency with itself'))
516             if self.impl_is_multi() != opt.impl_is_multi():
517                 raise ConfigError(_('every options in consistency should be '
518                                     'multi or none'))
519         func = '_cons_{0}'.format(func)
520         all_cons_opts = tuple([self] + list(other_opts))
521         value = self.impl_getdefault()
522         if value is not None:
523             if self.impl_is_multi():
524                 for idx, val in enumerate(value):
525                     self._launch_consistency(func, self, val, None,
526                                              idx, all_cons_opts)
527             else:
528                 self._launch_consistency(func, self, value, None,
529                                          None, all_cons_opts)
530         self._consistencies.append((func, all_cons_opts))
531         self.impl_validate(self.impl_getdefault())
532
533     def _cons_not_equal(self, opts, vals):
534         for idx_inf, val_inf in enumerate(vals):
535             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
536                 if val_inf == val_sup is not None:
537                     raise ValueError(_("same value for {0} and {1}").format(
538                         opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
539
540     def _impl_convert_callbacks(self, descr, load=False):
541         if not load and self._callback is None:
542             self._state_callback = None
543         elif load and self._state_callback is None:
544             self._callback = None
545             del(self._state_callback)
546         else:
547             if load:
548                 callback, callback_params = self._state_callback
549             else:
550                 callback, callback_params = self._callback
551             if callback_params is not None:
552                 cllbck_prms = {}
553                 for key, values in callback_params.items():
554                     vls = []
555                     for value in values:
556                         if isinstance(value, tuple):
557                             if load:
558                                 value = (descr.impl_get_opt_by_path(value[0]),
559                                          value[1])
560                             else:
561                                 value = (descr.impl_get_path_by_opt(value[0]),
562                                          value[1])
563                         vls.append(value)
564                     cllbck_prms[key] = tuple(vls)
565             else:
566                 cllbck_prms = None
567
568             if load:
569                 del(self._state_callback)
570                 self._callback = (callback, cllbck_prms)
571             else:
572                 self._state_callback = (callback, cllbck_prms)
573
574     # serialize/unserialize
575     def _impl_convert_consistencies(self, descr, load=False):
576         """during serialization process, many things have to be done.
577         one of them is the localisation of the options.
578         The paths are set once for all.
579
580         :type descr: :class:`tiramisu.option.OptionDescription`
581         :param load: `True` if we are at the init of the option description
582         :type load: bool
583         """
584         if not load and self._consistencies is None:
585             self._state_consistencies = None
586         elif load and self._state_consistencies is None:
587             self._consistencies = None
588             del(self._state_consistencies)
589         else:
590             if load:
591                 consistencies = self._state_consistencies
592             else:
593                 consistencies = self._consistencies
594             if isinstance(consistencies, list):
595                 new_value = []
596                 for consistency in consistencies:
597                     values = []
598                     for obj in consistency[1]:
599                         if load:
600                             values.append(descr.impl_get_opt_by_path(obj))
601                         else:
602                             values.append(descr.impl_get_path_by_opt(obj))
603                     new_value.append((consistency[0], tuple(values)))
604
605             else:
606                 new_value = {}
607                 for key, _consistencies in consistencies.items():
608                     new_value[key] = []
609                     for key_cons, _cons in _consistencies:
610                         _list_cons = []
611                         for _con in _cons:
612                             if load:
613                                 _list_cons.append(
614                                     descr.impl_get_opt_by_path(_con))
615                             else:
616                                 _list_cons.append(
617                                     descr.impl_get_path_by_opt(_con))
618                         new_value[key].append((key_cons, tuple(_list_cons)))
619             if load:
620                 del(self._state_consistencies)
621                 self._consistencies = new_value
622             else:
623                 self._state_consistencies = new_value
624
625     def _second_level_validation(self, value):
626         pass
627
628
629 class ChoiceOption(Option):
630     """represents a choice out of several objects.
631
632     The option can also have the value ``None``
633     """
634
635     __slots__ = ('_values', '_open_values')
636     _opt_type = 'string'
637
638     def __init__(self, name, doc, values, default=None, default_multi=None,
639                  requires=None, multi=False, callback=None,
640                  callback_params=None, open_values=False, validator=None,
641                  validator_params=None, properties=None, warnings_only=False):
642         """
643         :param values: is a list of values the option can possibly take
644         """
645         if not isinstance(values, tuple):
646             raise TypeError(_('values must be a tuple for {0}').format(name))
647         self._values = values
648         if open_values not in (True, False):
649             raise TypeError(_('open_values must be a boolean for '
650                             '{0}').format(name))
651         self._open_values = open_values
652         super(ChoiceOption, self).__init__(name, doc, default=default,
653                                            default_multi=default_multi,
654                                            callback=callback,
655                                            callback_params=callback_params,
656                                            requires=requires,
657                                            multi=multi,
658                                            validator=validator,
659                                            validator_params=validator_params,
660                                            properties=properties,
661                                            warnings_only=warnings_only)
662
663     def impl_get_values(self):
664         return self._values
665
666     def impl_is_openvalues(self):
667         return self._open_values
668
669     def _validate(self, value):
670         if not self._open_values and not value in self._values:
671             raise ValueError(_('value {0} is not permitted, '
672                                'only {1} is allowed'
673                                '').format(value, self._values))
674
675
676 class BoolOption(Option):
677     "represents a choice between ``True`` and ``False``"
678     __slots__ = tuple()
679     _opt_type = 'bool'
680
681     def _validate(self, value):
682         if not isinstance(value, bool):
683             raise ValueError(_('invalid boolean'))
684
685
686 class IntOption(Option):
687     "represents a choice of an integer"
688     __slots__ = tuple()
689     _opt_type = 'int'
690
691     def _validate(self, value):
692         if not isinstance(value, int):
693             raise ValueError(_('invalid integer'))
694
695
696 class FloatOption(Option):
697     "represents a choice of a floating point number"
698     __slots__ = tuple()
699     _opt_type = 'float'
700
701     def _validate(self, value):
702         if not isinstance(value, float):
703             raise ValueError(_('invalid float'))
704
705
706 class StrOption(Option):
707     "represents the choice of a string"
708     __slots__ = tuple()
709     _opt_type = 'string'
710
711     def _validate(self, value):
712         if not isinstance(value, str):
713             raise ValueError(_('invalid string'))
714
715
716 if sys.version_info[0] >= 3:
717     #UnicodeOption is same has StrOption in python 3+
718     class UnicodeOption(StrOption):
719         __slots__ = tuple()
720         pass
721 else:
722     class UnicodeOption(Option):
723         "represents the choice of a unicode string"
724         __slots__ = tuple()
725         _opt_type = 'unicode'
726         _empty = u''
727
728         def _validate(self, value):
729             if not isinstance(value, unicode):
730                 raise ValueError(_('invalid unicode'))
731
732
733 class SymLinkOption(BaseOption):
734     __slots__ = ('_name', '_opt', '_state_opt')
735     _opt_type = 'symlink'
736     #not return _opt consistencies
737     _consistencies = None
738
739     def __init__(self, name, opt):
740         self._name = name
741         if not isinstance(opt, Option):
742             raise ValueError(_('malformed symlinkoption '
743                                'must be an option '
744                                'for symlink {0}').format(name))
745         self._opt = opt
746         self._readonly = True
747
748     def __getattr__(self, name):
749         if name in ('_name', '_opt', '_opt_type', '_readonly'):
750             return object.__getattr__(self, name)
751         else:
752             return getattr(self._opt, name)
753
754     def _impl_getstate(self, descr):
755         super(SymLinkOption, self)._impl_getstate(descr)
756         self._state_opt = descr.impl_get_path_by_opt(self._opt)
757
758     def _impl_setstate(self, descr):
759         self._opt = descr.impl_get_opt_by_path(self._state_opt)
760         del(self._state_opt)
761         super(SymLinkOption, self)._impl_setstate(descr)
762
763
764 class IPOption(Option):
765     "represents the choice of an ip"
766     __slots__ = ('_private_only', '_allow_reserved')
767     _opt_type = 'ip'
768
769     def __init__(self, name, doc, default=None, default_multi=None,
770                  requires=None, multi=False, callback=None,
771                  callback_params=None, validator=None, validator_params=None,
772                  properties=None, private_only=False, allow_reserved=False,
773                  warnings_only=False):
774         self._private_only = private_only
775         self._allow_reserved = allow_reserved
776         super(IPOption, self).__init__(name, doc, default=default,
777                                        default_multi=default_multi,
778                                        callback=callback,
779                                        callback_params=callback_params,
780                                        requires=requires,
781                                        multi=multi,
782                                        validator=validator,
783                                        validator_params=validator_params,
784                                        properties=properties,
785                                        warnings_only=warnings_only)
786
787     def _validate(self, value):
788         # sometimes an ip term starts with a zero
789         # but this does not fit in some case, for example bind does not like it
790         for val in value.split('.'):
791             if val.startswith("0") and len(val)>1:
792                 raise ValueError(_('invalid IP'))
793         # 'standard' validation
794         try:
795             IP('{0}/32'.format(value))
796         except ValueError:
797             raise ValueError(_('invalid IP'))
798
799     def _second_level_validation(self, value):
800         ip = IP('{0}/32'.format(value))
801         if not self._allow_reserved and ip.iptype() == 'RESERVED':
802             raise ValueError(_("invalid IP, mustn't not be in reserved class"))
803         if self._private_only and not ip.iptype() == 'PRIVATE':
804             raise ValueError(_("invalid IP, must be in private class"))
805
806
807 class PortOption(Option):
808     """represents the choice of a port
809     The port numbers are divided into three ranges:
810     the well-known ports,
811     the registered ports,
812     and the dynamic or private ports.
813     You can actived this three range.
814     Port number 0 is reserved and can't be used.
815     see: http://en.wikipedia.org/wiki/Port_numbers
816     """
817     __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
818     _opt_type = 'port'
819
820     def __init__(self, name, doc, default=None, default_multi=None,
821                  requires=None, multi=False, callback=None,
822                  callback_params=None, validator=None, validator_params=None,
823                  properties=None, allow_range=False, allow_zero=False,
824                  allow_wellknown=True, allow_registred=True,
825                  allow_private=False, warnings_only=False):
826         self._allow_range = allow_range
827         self._min_value = None
828         self._max_value = None
829         ports_min = [0, 1, 1024, 49152]
830         ports_max = [0, 1023, 49151, 65535]
831         is_finally = False
832         for index, allowed in enumerate([allow_zero,
833                                          allow_wellknown,
834                                          allow_registred,
835                                          allow_private]):
836             if self._min_value is None:
837                 if allowed:
838                     self._min_value = ports_min[index]
839             elif not allowed:
840                 is_finally = True
841             elif allowed and is_finally:
842                 raise ValueError(_('inconsistency in allowed range'))
843             if allowed:
844                 self._max_value = ports_max[index]
845
846         if self._max_value is None:
847             raise ValueError(_('max value is empty'))
848
849         super(PortOption, self).__init__(name, doc, default=default,
850                                          default_multi=default_multi,
851                                          callback=callback,
852                                          callback_params=callback_params,
853                                          requires=requires,
854                                          multi=multi,
855                                          validator=validator,
856                                          validator_params=validator_params,
857                                          properties=properties,
858                                          warnings_only=warnings_only)
859
860     def _validate(self, value):
861         if self._allow_range and ":" in str(value):
862             value = str(value).split(':')
863             if len(value) != 2:
864                 raise ValueError('invalid part, range must have two values '
865                                  'only')
866             if not value[0] < value[1]:
867                 raise ValueError('invalid port, first port in range must be'
868                                  ' smaller than the second one')
869         else:
870             value = [value]
871
872         for val in value:
873             if not self._min_value <= int(val) <= self._max_value:
874                 raise ValueError('invalid port, must be an between {0} and {1}'
875                                  ''.format(self._min_value, self._max_value))
876
877
878 class NetworkOption(Option):
879     "represents the choice of a network"
880     __slots__ = tuple()
881     _opt_type = 'network'
882
883     def _validate(self, value):
884         try:
885             IP(value)
886         except ValueError:
887             raise ValueError(_('invalid network address'))
888
889     def _second_level_validation(self, value):
890         ip = IP(value)
891         if ip.iptype() == 'RESERVED':
892             raise ValueError(_("invalid network address, must not be in reserved class"))
893
894
895 class NetmaskOption(Option):
896     "represents the choice of a netmask"
897     __slots__ = tuple()
898     _opt_type = 'netmask'
899
900     def _validate(self, value):
901         try:
902             IP('0.0.0.0/{0}'.format(value))
903         except ValueError:
904             raise ValueError(_('invalid netmask address'))
905
906     def _cons_network_netmask(self, opts, vals):
907         #opts must be (netmask, network) options
908         if None in vals:
909             return
910         self.__cons_netmask(opts, vals[0], vals[1], False)
911
912     def _cons_ip_netmask(self, opts, vals):
913         #opts must be (netmask, ip) options
914         if None in vals:
915             return
916         self.__cons_netmask(opts, vals[0], vals[1], True)
917
918     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
919         if len(opts) != 2:
920             raise ConfigError(_('invalid len for opts'))
921         msg = None
922         try:
923             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
924                     make_net=make_net)
925             #if cidr == 32, ip same has network
926             if ip.prefixlen() != 32:
927                 try:
928                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
929                         make_net=not make_net)
930                 except ValueError:
931                     if not make_net:
932                         msg = _("invalid network {0} ({1}) "
933                                 "with netmask {2},"
934                                 " this network is an IP")
935                 else:
936                     if make_net:
937                         msg = _("invalid IP {0} ({1}) with netmask {2},"
938                                 " this IP is a network")
939
940         except ValueError:
941             if make_net:
942                 msg = _('invalid IP {0} ({1}) with netmask {2}')
943             else:
944                 msg = _('invalid network {0} ({1}) with netmask {2}')
945         if msg is not None:
946             raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
947                                         val_netmask))
948
949
950 class BroadcastOption(Option):
951     __slots__ = tuple()
952     _opt_type = 'broadcast'
953
954     def _validate(self, value):
955         try:
956             IP('{0}/32'.format(value))
957         except ValueError:
958             raise ValueError(_('invalid broadcast address'))
959
960     def _cons_broadcast(self, opts, vals):
961         if len(vals) != 3:
962             raise ConfigError(_('invalid len for vals'))
963         if None in vals:
964             return
965         broadcast, network, netmask = vals
966         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
967             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
968                                '({3}) and netmask {4} ({5})').format(
969                                    broadcast, opts[0]._name, network,
970                                    opts[1]._name, netmask, opts[2]._name))
971
972
973 class DomainnameOption(Option):
974     """represents the choice of a domain name
975     netbios: for MS domain
976     hostname: to identify the device
977     domainname:
978     fqdn: with tld, not supported yet
979     """
980     __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
981     _opt_type = 'domainname'
982
983     def __init__(self, name, doc, default=None, default_multi=None,
984                  requires=None, multi=False, callback=None,
985                  callback_params=None, validator=None, validator_params=None,
986                  properties=None, allow_ip=False, type_='domainname',
987                  warnings_only=False, allow_without_dot=False):
988         if type_ not in ['netbios', 'hostname', 'domainname']:
989             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
990         self._type = type_
991         if allow_ip not in [True, False]:
992             raise ValueError(_('allow_ip must be a boolean'))
993         if allow_without_dot not in [True, False]:
994             raise ValueError(_('allow_without_dot must be a boolean'))
995         self._allow_ip = allow_ip
996         self._allow_without_dot = allow_without_dot
997         end = ''
998         extrachar = ''
999         extrachar_mandatory = ''
1000         if self._type == 'netbios':
1001             length = 14
1002         elif self._type == 'hostname':
1003             length = 62
1004         elif self._type == 'domainname':
1005             length = 62
1006             if allow_without_dot is False:
1007                 extrachar_mandatory = '\.'
1008             else:
1009                 extrachar = '\.'
1010             end = '+[a-z]*'
1011         self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-{0}]{{,{1}}}{2}){3}$'
1012                                      ''.format(extrachar, length, extrachar_mandatory, end))
1013         super(DomainnameOption, self).__init__(name, doc, default=default,
1014                                                default_multi=default_multi,
1015                                                callback=callback,
1016                                                callback_params=callback_params,
1017                                                requires=requires,
1018                                                multi=multi,
1019                                                validator=validator,
1020                                                validator_params=validator_params,
1021                                                properties=properties,
1022                                                warnings_only=warnings_only)
1023
1024     def _validate(self, value):
1025         if self._allow_ip is True:
1026             try:
1027                 IP('{0}/32'.format(value))
1028                 return
1029             except ValueError:
1030                 pass
1031         if self._type == 'domainname' and not self._allow_without_dot and \
1032                 '.' not in value:
1033             raise ValueError(_("invalid domainname, must have dot"))
1034             if len(value) > 255:
1035                 raise ValueError(_("invalid domainname's length (max 255)"))
1036         if len(value) < 2:
1037             raise ValueError(_("invalid domainname's length (min 2)"))
1038         if not self._domain_re.search(value):
1039             raise ValueError(_('invalid domainname'))
1040
1041
1042 class EmailOption(DomainnameOption):
1043     __slots__ = tuple()
1044     _opt_type = 'email'
1045     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1046
1047     def _validate(self, value):
1048         splitted = value.split('@', 1)
1049         try:
1050             username, domain = splitted
1051         except ValueError:
1052             raise ValueError(_('invalid email address, should contains one @'
1053                                ))
1054         if not self.username_re.search(username):
1055             raise ValueError(_('invalid username in email address'))
1056         super(EmailOption, self)._validate(domain)
1057
1058
1059 class URLOption(DomainnameOption):
1060     __slots__ = tuple()
1061     _opt_type = 'url'
1062     proto_re = re.compile(r'(http|https)://')
1063     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1064
1065     def _validate(self, value):
1066         match = self.proto_re.search(value)
1067         if not match:
1068             raise ValueError(_('invalid url, should start with http:// or '
1069                                'https://'))
1070         value = value[len(match.group(0)):]
1071         # get domain/files
1072         splitted = value.split('/', 1)
1073         try:
1074             domain, files = splitted
1075         except ValueError:
1076             domain = value
1077             files = None
1078         # if port in domain
1079         splitted = domain.split(':', 1)
1080         try:
1081             domain, port = splitted
1082
1083         except ValueError:
1084             domain = splitted[0]
1085             port = 0
1086         if not 0 <= int(port) <= 65535:
1087             raise ValueError(_('invalid url, port must be an between 0 and '
1088                                '65536'))
1089         # validate domainname
1090         super(URLOption, self)._validate(domain)
1091         # validate file
1092         if files is not None and files != '' and not self.path_re.search(files):
1093             raise ValueError(_('invalid url, should ends with filename'))
1094
1095
1096 class FilenameOption(Option):
1097     __slots__ = tuple()
1098     _opt_type = 'file'
1099     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1100
1101     def _validate(self, value):
1102         match = self.path_re.search(value)
1103         if not match:
1104             raise ValueError(_('invalid filename'))
1105
1106
1107 class OptionDescription(BaseOption):
1108     """Config's schema (organisation, group) and container of Options
1109     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1110     """
1111     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
1112                  '_state_group_type', '_properties', '_children',
1113                  '_cache_consistencies', '_calc_properties', '__weakref__',
1114                  '_readonly', '_impl_informations', '_state_requires',
1115                  '_stated', '_state_readonly')
1116     _opt_type = 'optiondescription'
1117
1118     def __init__(self, name, doc, children, requires=None, properties=None):
1119         """
1120         :param children: a list of options (including optiondescriptions)
1121
1122         """
1123         super(OptionDescription, self).__init__(name, doc, requires, properties)
1124         child_names = [child._name for child in children]
1125         #better performance like this
1126         valid_child = copy(child_names)
1127         valid_child.sort()
1128         old = None
1129         for child in valid_child:
1130             if child == old:
1131                 raise ConflictError(_('duplicate option name: '
1132                                       '{0}').format(child))
1133             old = child
1134         self._children = (tuple(child_names), tuple(children))
1135         self._cache_paths = None
1136         self._cache_consistencies = None
1137         # the group_type is useful for filtering OptionDescriptions in a config
1138         self._group_type = groups.default
1139
1140     def impl_getdoc(self):
1141         return self.impl_get_information('doc')
1142
1143     def __getattr__(self, name):
1144         if name in self.__slots__:
1145             return object.__getattribute__(self, name)
1146         try:
1147             return self._children[1][self._children[0].index(name)]
1148         except ValueError:
1149             raise AttributeError(_('unknown Option {0} '
1150                                    'in OptionDescription {1}'
1151                                    '').format(name, self._name))
1152
1153     def impl_getkey(self, config):
1154         return tuple([child.impl_getkey(getattr(config, child._name))
1155                       for child in self.impl_getchildren()])
1156
1157     def impl_getpaths(self, include_groups=False, _currpath=None):
1158         """returns a list of all paths in self, recursively
1159            _currpath should not be provided (helps with recursion)
1160         """
1161         if _currpath is None:
1162             _currpath = []
1163         paths = []
1164         for option in self.impl_getchildren():
1165             attr = option._name
1166             if isinstance(option, OptionDescription):
1167                 if include_groups:
1168                     paths.append('.'.join(_currpath + [attr]))
1169                 paths += option.impl_getpaths(include_groups=include_groups,
1170                                               _currpath=_currpath + [attr])
1171             else:
1172                 paths.append('.'.join(_currpath + [attr]))
1173         return paths
1174
1175     def impl_getchildren(self):
1176         return self._children[1]
1177
1178     def impl_build_cache(self,
1179                          cache_path=None,
1180                          cache_option=None,
1181                          _currpath=None,
1182                          _consistencies=None,
1183                          force_no_consistencies=False):
1184         if _currpath is None and self._cache_paths is not None:
1185             # cache already set
1186             return
1187         if _currpath is None:
1188             save = True
1189             _currpath = []
1190             if not force_no_consistencies:
1191                 _consistencies = {}
1192         else:
1193             save = False
1194         if cache_path is None:
1195             cache_path = []
1196             cache_option = []
1197         for option in self.impl_getchildren():
1198             attr = option._name
1199             if option in cache_option:
1200                 raise ConflictError(_('duplicate option: {0}').format(option))
1201
1202             cache_option.append(option)
1203             if not force_no_consistencies:
1204                 option._readonly = True
1205             cache_path.append(str('.'.join(_currpath + [attr])))
1206             if not isinstance(option, OptionDescription):
1207                 if not force_no_consistencies and \
1208                         option._consistencies is not None:
1209                     for consistency in option._consistencies:
1210                         func, all_cons_opts = consistency
1211                         for opt in all_cons_opts:
1212                             _consistencies.setdefault(opt,
1213                                                       []).append((func,
1214                                                                   all_cons_opts))
1215             else:
1216                 _currpath.append(attr)
1217                 option.impl_build_cache(cache_path,
1218                                         cache_option,
1219                                         _currpath,
1220                                         _consistencies,
1221                                         force_no_consistencies)
1222                 _currpath.pop()
1223         if save:
1224             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1225             if not force_no_consistencies:
1226                 if _consistencies != {}:
1227                     self._cache_consistencies = {}
1228                     for opt, cons in _consistencies.items():
1229                         if opt not in cache_option:
1230                             raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
1231                         self._cache_consistencies[opt] = tuple(cons)
1232                 self._readonly = True
1233
1234     def impl_get_opt_by_path(self, path):
1235         try:
1236             return self._cache_paths[0][self._cache_paths[1].index(path)]
1237         except ValueError:
1238             raise AttributeError(_('no option for path {0}').format(path))
1239
1240     def impl_get_path_by_opt(self, opt):
1241         try:
1242             return self._cache_paths[1][self._cache_paths[0].index(opt)]
1243         except ValueError:
1244             raise AttributeError(_('no option {0} found').format(opt))
1245
1246     # ____________________________________________________________
1247     def impl_set_group_type(self, group_type):
1248         """sets a given group object to an OptionDescription
1249
1250         :param group_type: an instance of `GroupType` or `MasterGroupType`
1251                               that lives in `setting.groups`
1252         """
1253         if self._group_type != groups.default:
1254             raise TypeError(_('cannot change group_type if already set '
1255                             '(old {0}, new {1})').format(self._group_type,
1256                                                          group_type))
1257         if isinstance(group_type, groups.GroupType):
1258             self._group_type = group_type
1259             if isinstance(group_type, groups.MasterGroupType):
1260                 #if master (same name has group) is set
1261                 identical_master_child_name = False
1262                 #for collect all slaves
1263                 slaves = []
1264                 master = None
1265                 for child in self.impl_getchildren():
1266                     if isinstance(child, OptionDescription):
1267                         raise ValueError(_("master group {0} shall not have "
1268                                          "a subgroup").format(self._name))
1269                     if isinstance(child, SymLinkOption):
1270                         raise ValueError(_("master group {0} shall not have "
1271                                          "a symlinkoption").format(self._name))
1272                     if not child.impl_is_multi():
1273                         raise ValueError(_("not allowed option {0} "
1274                                          "in group {1}"
1275                                          ": this option is not a multi"
1276                                          "").format(child._name, self._name))
1277                     if child._name == self._name:
1278                         identical_master_child_name = True
1279                         child._multitype = multitypes.master
1280                         master = child
1281                     else:
1282                         slaves.append(child)
1283                 if master is None:
1284                     raise ValueError(_('master group with wrong'
1285                                        ' master name for {0}'
1286                                        ).format(self._name))
1287                 master._master_slaves = tuple(slaves)
1288                 for child in self.impl_getchildren():
1289                     if child != master:
1290                         child._master_slaves = master
1291                         child._multitype = multitypes.slave
1292                 if not identical_master_child_name:
1293                     raise ValueError(_("no child has same nom has master group"
1294                                        " for: {0}").format(self._name))
1295         else:
1296             raise ValueError(_('group_type: {0}'
1297                                ' not allowed').format(group_type))
1298
1299     def impl_get_group_type(self):
1300         return self._group_type
1301
1302     def _valid_consistency(self, option, value, context, index):
1303         if self._cache_consistencies is None:
1304             return True
1305         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1306         consistencies = self._cache_consistencies.get(option)
1307         if consistencies is not None:
1308             for func, all_cons_opts in consistencies:
1309                 #all_cons_opts[0] is the option where func is set
1310                 ret = all_cons_opts[0]._launch_consistency(func, option,
1311                                                            value,
1312                                                            context, index,
1313                                                            all_cons_opts)
1314                 if ret is False:
1315                     return False
1316         return True
1317
1318     def _impl_getstate(self, descr=None):
1319         """enables us to export into a dict
1320         :param descr: parent :class:`tiramisu.option.OptionDescription`
1321         """
1322         if descr is None:
1323             self.impl_build_cache()
1324             descr = self
1325         super(OptionDescription, self)._impl_getstate(descr)
1326         self._state_group_type = str(self._group_type)
1327         for option in self.impl_getchildren():
1328             option._impl_getstate(descr)
1329
1330     def __getstate__(self):
1331         """special method to enable the serialization with pickle
1332         """
1333         stated = True
1334         try:
1335             # the `_state` attribute is a flag that which tells us if
1336             # the serialization can be performed
1337             self._stated
1338         except AttributeError:
1339             # if cannot delete, _impl_getstate never launch
1340             # launch it recursivement
1341             # _stated prevent __getstate__ launch more than one time
1342             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1343             self._impl_getstate()
1344             stated = False
1345         return super(OptionDescription, self).__getstate__(stated)
1346
1347     def _impl_setstate(self, descr=None):
1348         """enables us to import from a dict
1349         :param descr: parent :class:`tiramisu.option.OptionDescription`
1350         """
1351         if descr is None:
1352             self._cache_paths = None
1353             self._cache_consistencies = None
1354             self.impl_build_cache(force_no_consistencies=True)
1355             descr = self
1356         self._group_type = getattr(groups, self._state_group_type)
1357         del(self._state_group_type)
1358         super(OptionDescription, self)._impl_setstate(descr)
1359         for option in self.impl_getchildren():
1360             option._impl_setstate(descr)
1361
1362     def __setstate__(self, state):
1363         super(OptionDescription, self).__setstate__(state)
1364         try:
1365             self._stated
1366         except AttributeError:
1367             self._impl_setstate()
1368
1369
1370 def validate_requires_arg(requires, name):
1371     """check malformed requirements
1372     and tranform dict to internal tuple
1373
1374     :param requires: have a look at the
1375                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1376                      know more about
1377                      the description of the requires dictionary
1378     """
1379     if requires is None:
1380         return None, None
1381     ret_requires = {}
1382     config_action = {}
1383
1384     # start parsing all requires given by user (has dict)
1385     # transforme it to a tuple
1386     for require in requires:
1387         if not type(require) == dict:
1388             raise ValueError(_("malformed requirements type for option:"
1389                                " {0}, must be a dict").format(name))
1390         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1391                       'same_action')
1392         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1393         if unknown_keys != frozenset():
1394             raise ValueError('malformed requirements for option: {0}'
1395                              ' unknown keys {1}, must only '
1396                              '{2}'.format(name,
1397                                           unknown_keys,
1398                                           valid_keys))
1399         # prepare all attributes
1400         try:
1401             option = require['option']
1402             expected = require['expected']
1403             action = require['action']
1404         except KeyError:
1405             raise ValueError(_("malformed requirements for option: {0}"
1406                                " require must have option, expected and"
1407                                " action keys").format(name))
1408         inverse = require.get('inverse', False)
1409         if inverse not in [True, False]:
1410             raise ValueError(_('malformed requirements for option: {0}'
1411                                ' inverse must be boolean'))
1412         transitive = require.get('transitive', True)
1413         if transitive not in [True, False]:
1414             raise ValueError(_('malformed requirements for option: {0}'
1415                                ' transitive must be boolean'))
1416         same_action = require.get('same_action', True)
1417         if same_action not in [True, False]:
1418             raise ValueError(_('malformed requirements for option: {0}'
1419                                ' same_action must be boolean'))
1420
1421         if not isinstance(option, Option):
1422             raise ValueError(_('malformed requirements '
1423                                'must be an option in option {0}').format(name))
1424         if option.impl_is_multi():
1425             raise ValueError(_('malformed requirements option {0} '
1426                                'should not be a multi').format(name))
1427         if expected is not None:
1428             try:
1429                 option._validate(expected)
1430             except ValueError as err:
1431                 raise ValueError(_('malformed requirements second argument '
1432                                    'must be valid for option {0}'
1433                                    ': {1}').format(name, err))
1434         if action in config_action:
1435             if inverse != config_action[action]:
1436                 raise ValueError(_("inconsistency in action types"
1437                                    " for option: {0}"
1438                                    " action: {1}").format(name, action))
1439         else:
1440             config_action[action] = inverse
1441         if action not in ret_requires:
1442             ret_requires[action] = {}
1443         if option not in ret_requires[action]:
1444             ret_requires[action][option] = (option, [expected], action,
1445                                             inverse, transitive, same_action)
1446         else:
1447             ret_requires[action][option][1].append(expected)
1448     # transform dict to tuple
1449     ret = []
1450     for opt_requires in ret_requires.values():
1451         ret_action = []
1452         for require in opt_requires.values():
1453             ret_action.append((require[0], tuple(require[1]), require[2],
1454                                require[3], require[4], require[5]))
1455         ret.append(tuple(ret_action))
1456     return frozenset(config_action.keys()), tuple(ret)
1457
1458
1459 def validate_callback(callback, callback_params, type_):
1460     if type(callback) != FunctionType:
1461         raise ValueError(_('{0} should be a function').format(type_))
1462     if callback_params is not None:
1463         if not isinstance(callback_params, dict):
1464             raise ValueError(_('{0}_params should be a dict').format(type_))
1465         for key, callbacks in callback_params.items():
1466             if key != '' and len(callbacks) != 1:
1467                 raise ValueError(_('{0}_params with key {1} should not have '
1468                                    'length different to 1').format(type_,
1469                                                                    key))
1470             if not isinstance(callbacks, tuple):
1471                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1472                                    ).format(type_, key))
1473             for callbk in callbacks:
1474                 if isinstance(callbk, tuple):
1475                     option, force_permissive = callbk
1476                     if type_ == 'validator' and not force_permissive:
1477                         raise ValueError(_('validator not support tuple'))
1478                     if not isinstance(option, Option) and not \
1479                             isinstance(option, SymLinkOption):
1480                         raise ValueError(_('{0}_params should have an option '
1481                                            'not a {0} for first argument'
1482                                            ).format(type_, type(option)))
1483                     if force_permissive not in [True, False]:
1484                         raise ValueError(_('{0}_params should have a boolean'
1485                                            ' not a {0} for second argument'
1486                                            ).format(type_, type(
1487                                                force_permissive)))