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