Merge branch 'master' of ssh://git.labs.libre-entreprise.org/gitroot/tiramisu
[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                 int(val)
856             except ValueError:
857                 raise ValueError(_('invalid port'))
858             if not self._min_value <= int(val) <= self._max_value:
859                 raise ValueError(_('invalid port, must be an between {0} '
860                                     'and {1}').format(self._min_value,
861                                                     self._max_value))
862
863
864 class NetworkOption(Option):
865     "represents the choice of a network"
866     __slots__ = tuple()
867     _opt_type = 'network'
868
869     def _validate(self, value):
870         try:
871             IP(value)
872         except ValueError:
873             raise ValueError(_('invalid network address'))
874
875     def _second_level_validation(self, value):
876         ip = IP(value)
877         if ip.iptype() == 'RESERVED':
878             raise ValueError(_("invalid network address, must not be in reserved class"))
879
880
881 class NetmaskOption(Option):
882     "represents the choice of a netmask"
883     __slots__ = tuple()
884     _opt_type = 'netmask'
885
886     def _validate(self, value):
887         try:
888             IP('0.0.0.0/{0}'.format(value))
889         except ValueError:
890             raise ValueError(_('invalid netmask address'))
891
892     def _cons_network_netmask(self, opts, vals):
893         #opts must be (netmask, network) options
894         if None in vals:
895             return
896         self.__cons_netmask(opts, vals[0], vals[1], False)
897
898     def _cons_ip_netmask(self, opts, vals):
899         #opts must be (netmask, ip) options
900         if None in vals:
901             return
902         self.__cons_netmask(opts, vals[0], vals[1], True)
903
904     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
905         if len(opts) != 2:
906             raise ConfigError(_('invalid len for opts'))
907         msg = None
908         try:
909             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
910                     make_net=make_net)
911             #if cidr == 32, ip same has network
912             if ip.prefixlen() != 32:
913                 try:
914                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
915                         make_net=not make_net)
916                 except ValueError:
917                     pass
918                 else:
919                     if make_net:
920                         msg = _("invalid IP {0} ({1}) with netmask {2},"
921                                 " this IP is a network")
922
923         except ValueError:
924             if not make_net:
925                 msg = _('invalid network {0} ({1}) with netmask {2}')
926         if msg is not None:
927             raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
928                                         val_netmask))
929
930
931 class BroadcastOption(Option):
932     __slots__ = tuple()
933     _opt_type = 'broadcast'
934
935     def _validate(self, value):
936         try:
937             IP('{0}/32'.format(value))
938         except ValueError:
939             raise ValueError(_('invalid broadcast address'))
940
941     def _cons_broadcast(self, opts, vals):
942         if len(vals) != 3:
943             raise ConfigError(_('invalid len for vals'))
944         if None in vals:
945             return
946         broadcast, network, netmask = vals
947         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
948             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
949                                '({3}) and netmask {4} ({5})').format(
950                                    broadcast, opts[0]._name, network,
951                                    opts[1]._name, netmask, opts[2]._name))
952
953
954 class DomainnameOption(Option):
955     """represents the choice of a domain name
956     netbios: for MS domain
957     hostname: to identify the device
958     domainname:
959     fqdn: with tld, not supported yet
960     """
961     __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
962     _opt_type = 'domainname'
963
964     def __init__(self, name, doc, default=None, default_multi=None,
965                  requires=None, multi=False, callback=None,
966                  callback_params=None, validator=None, validator_params=None,
967                  properties=None, allow_ip=False, type_='domainname',
968                  warnings_only=False, allow_without_dot=False):
969         if type_ not in ['netbios', 'hostname', 'domainname']:
970             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
971         self._type = type_
972         if allow_ip not in [True, False]:
973             raise ValueError(_('allow_ip must be a boolean'))
974         if allow_without_dot not in [True, False]:
975             raise ValueError(_('allow_without_dot must be a boolean'))
976         self._allow_ip = allow_ip
977         self._allow_without_dot = allow_without_dot
978         end = ''
979         extrachar = ''
980         extrachar_mandatory = ''
981         if self._type != 'netbios':
982             allow_number = '\d'
983         else:
984             allow_number = ''
985         if self._type == 'netbios':
986             length = 14
987         elif self._type == 'hostname':
988             length = 62
989         elif self._type == 'domainname':
990             length = 62
991             if allow_without_dot is False:
992                 extrachar_mandatory = '\.'
993             else:
994                 extrachar = '\.'
995             end = '+[a-z]*'
996         self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
997                                      ''.format(allow_number, extrachar, length,
998                                                extrachar_mandatory, end))
999         super(DomainnameOption, self).__init__(name, doc, default=default,
1000                                                default_multi=default_multi,
1001                                                callback=callback,
1002                                                callback_params=callback_params,
1003                                                requires=requires,
1004                                                multi=multi,
1005                                                validator=validator,
1006                                                validator_params=validator_params,
1007                                                properties=properties,
1008                                                warnings_only=warnings_only)
1009
1010     def _validate(self, value):
1011         if self._allow_ip is True:
1012             try:
1013                 IP('{0}/32'.format(value))
1014                 return
1015             except ValueError:
1016                 pass
1017         if self._type == 'domainname' and not self._allow_without_dot and \
1018                 '.' not in value:
1019             raise ValueError(_("invalid domainname, must have dot"))
1020         if len(value) > 255:
1021             raise ValueError(_("invalid domainname's length (max 255)"))
1022         if len(value) < 2:
1023             raise ValueError(_("invalid domainname's length (min 2)"))
1024         if not self._domain_re.search(value):
1025             raise ValueError(_('invalid domainname'))
1026
1027
1028 class EmailOption(DomainnameOption):
1029     __slots__ = tuple()
1030     _opt_type = 'email'
1031     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1032
1033     def _validate(self, value):
1034         splitted = value.split('@', 1)
1035         try:
1036             username, domain = splitted
1037         except ValueError:
1038             raise ValueError(_('invalid email address, should contains one @'
1039                                ))
1040         if not self.username_re.search(username):
1041             raise ValueError(_('invalid username in email address'))
1042         super(EmailOption, self)._validate(domain)
1043
1044
1045 class URLOption(DomainnameOption):
1046     __slots__ = tuple()
1047     _opt_type = 'url'
1048     proto_re = re.compile(r'(http|https)://')
1049     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1050
1051     def _validate(self, value):
1052         match = self.proto_re.search(value)
1053         if not match:
1054             raise ValueError(_('invalid url, should start with http:// or '
1055                                'https://'))
1056         value = value[len(match.group(0)):]
1057         # get domain/files
1058         splitted = value.split('/', 1)
1059         try:
1060             domain, files = splitted
1061         except ValueError:
1062             domain = value
1063             files = None
1064         # if port in domain
1065         splitted = domain.split(':', 1)
1066         try:
1067             domain, port = splitted
1068
1069         except ValueError:
1070             domain = splitted[0]
1071             port = 0
1072         if not 0 <= int(port) <= 65535:
1073             raise ValueError(_('invalid url, port must be an between 0 and '
1074                                '65536'))
1075         # validate domainname
1076         super(URLOption, self)._validate(domain)
1077         # validate file
1078         if files is not None and files != '' and not self.path_re.search(files):
1079             raise ValueError(_('invalid url, should ends with filename'))
1080
1081
1082 class FilenameOption(Option):
1083     __slots__ = tuple()
1084     _opt_type = 'file'
1085     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1086
1087     def _validate(self, value):
1088         match = self.path_re.search(value)
1089         if not match:
1090             raise ValueError(_('invalid filename'))
1091
1092
1093 class OptionDescription(BaseOption):
1094     """Config's schema (organisation, group) and container of Options
1095     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1096     """
1097     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
1098                  '_state_group_type', '_properties', '_children',
1099                  '_cache_consistencies', '_calc_properties', '__weakref__',
1100                  '_readonly', '_impl_informations', '_state_requires',
1101                  '_stated', '_state_readonly')
1102     _opt_type = 'optiondescription'
1103
1104     def __init__(self, name, doc, children, requires=None, properties=None):
1105         """
1106         :param children: a list of options (including optiondescriptions)
1107
1108         """
1109         super(OptionDescription, self).__init__(name, doc, requires, properties)
1110         child_names = [child._name for child in children]
1111         #better performance like this
1112         valid_child = copy(child_names)
1113         valid_child.sort()
1114         old = None
1115         for child in valid_child:
1116             if child == old:
1117                 raise ConflictError(_('duplicate option name: '
1118                                       '{0}').format(child))
1119             old = child
1120         self._children = (tuple(child_names), tuple(children))
1121         self._cache_paths = None
1122         self._cache_consistencies = None
1123         # the group_type is useful for filtering OptionDescriptions in a config
1124         self._group_type = groups.default
1125
1126     def impl_getdoc(self):
1127         return self.impl_get_information('doc')
1128
1129     def __getattr__(self, name):
1130         if name in self.__slots__:
1131             return object.__getattribute__(self, name)
1132         try:
1133             return self._children[1][self._children[0].index(name)]
1134         except ValueError:
1135             raise AttributeError(_('unknown Option {0} '
1136                                    'in OptionDescription {1}'
1137                                    '').format(name, self._name))
1138
1139     def impl_getkey(self, config):
1140         return tuple([child.impl_getkey(getattr(config, child._name))
1141                       for child in self.impl_getchildren()])
1142
1143     def impl_getpaths(self, include_groups=False, _currpath=None):
1144         """returns a list of all paths in self, recursively
1145            _currpath should not be provided (helps with recursion)
1146         """
1147         if _currpath is None:
1148             _currpath = []
1149         paths = []
1150         for option in self.impl_getchildren():
1151             attr = option._name
1152             if isinstance(option, OptionDescription):
1153                 if include_groups:
1154                     paths.append('.'.join(_currpath + [attr]))
1155                 paths += option.impl_getpaths(include_groups=include_groups,
1156                                               _currpath=_currpath + [attr])
1157             else:
1158                 paths.append('.'.join(_currpath + [attr]))
1159         return paths
1160
1161     def impl_getchildren(self):
1162         return self._children[1]
1163
1164     def impl_build_cache(self,
1165                          cache_path=None,
1166                          cache_option=None,
1167                          _currpath=None,
1168                          _consistencies=None,
1169                          force_no_consistencies=False):
1170         if _currpath is None and self._cache_paths is not None:
1171             # cache already set
1172             return
1173         if _currpath is None:
1174             save = True
1175             _currpath = []
1176             if not force_no_consistencies:
1177                 _consistencies = {}
1178         else:
1179             save = False
1180         if cache_path is None:
1181             cache_path = []
1182             cache_option = []
1183         for option in self.impl_getchildren():
1184             attr = option._name
1185             if option in cache_option:
1186                 raise ConflictError(_('duplicate option: {0}').format(option))
1187
1188             cache_option.append(option)
1189             if not force_no_consistencies:
1190                 option._readonly = True
1191             cache_path.append(str('.'.join(_currpath + [attr])))
1192             if not isinstance(option, OptionDescription):
1193                 if not force_no_consistencies and \
1194                         option._consistencies is not None:
1195                     for consistency in option._consistencies:
1196                         func, all_cons_opts = consistency
1197                         for opt in all_cons_opts:
1198                             _consistencies.setdefault(opt,
1199                                                       []).append((func,
1200                                                                   all_cons_opts))
1201             else:
1202                 _currpath.append(attr)
1203                 option.impl_build_cache(cache_path,
1204                                         cache_option,
1205                                         _currpath,
1206                                         _consistencies,
1207                                         force_no_consistencies)
1208                 _currpath.pop()
1209         if save:
1210             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1211             if not force_no_consistencies:
1212                 if _consistencies != {}:
1213                     self._cache_consistencies = {}
1214                     for opt, cons in _consistencies.items():
1215                         if opt not in cache_option:
1216                             raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
1217                         self._cache_consistencies[opt] = tuple(cons)
1218                 self._readonly = True
1219
1220     def impl_get_opt_by_path(self, path):
1221         try:
1222             return self._cache_paths[0][self._cache_paths[1].index(path)]
1223         except ValueError:
1224             raise AttributeError(_('no option for path {0}').format(path))
1225
1226     def impl_get_path_by_opt(self, opt):
1227         try:
1228             return self._cache_paths[1][self._cache_paths[0].index(opt)]
1229         except ValueError:
1230             raise AttributeError(_('no option {0} found').format(opt))
1231
1232     # ____________________________________________________________
1233     def impl_set_group_type(self, group_type):
1234         """sets a given group object to an OptionDescription
1235
1236         :param group_type: an instance of `GroupType` or `MasterGroupType`
1237                               that lives in `setting.groups`
1238         """
1239         if self._group_type != groups.default:
1240             raise TypeError(_('cannot change group_type if already set '
1241                             '(old {0}, new {1})').format(self._group_type,
1242                                                          group_type))
1243         if isinstance(group_type, groups.GroupType):
1244             self._group_type = group_type
1245             if isinstance(group_type, groups.MasterGroupType):
1246                 #if master (same name has group) is set
1247                 #for collect all slaves
1248                 slaves = []
1249                 master = None
1250                 for child in self.impl_getchildren():
1251                     if isinstance(child, OptionDescription):
1252                         raise ValueError(_("master group {0} shall not have "
1253                                          "a subgroup").format(self._name))
1254                     if isinstance(child, SymLinkOption):
1255                         raise ValueError(_("master group {0} shall not have "
1256                                          "a symlinkoption").format(self._name))
1257                     if not child.impl_is_multi():
1258                         raise ValueError(_("not allowed option {0} "
1259                                          "in group {1}"
1260                                          ": this option is not a multi"
1261                                          "").format(child._name, self._name))
1262                     if child._name == self._name:
1263                         child._multitype = multitypes.master
1264                         master = child
1265                     else:
1266                         slaves.append(child)
1267                 if master is None:
1268                     raise ValueError(_('master group with wrong'
1269                                        ' master name for {0}'
1270                                        ).format(self._name))
1271                 if master._callback is not None and master._callback[1] is not None:
1272                     for key, callbacks in master._callback[1].items():
1273                         for callbk in callbacks:
1274                             if isinstance(callbk, tuple):
1275                                 if callbk[0] in slaves:
1276                                     raise ValueError(_("callback of master's option shall "
1277                                                        "not refered a slave's ones"))
1278                 master._master_slaves = tuple(slaves)
1279                 for child in self.impl_getchildren():
1280                     if child != master:
1281                         child._master_slaves = master
1282                         child._multitype = multitypes.slave
1283         else:
1284             raise ValueError(_('group_type: {0}'
1285                                ' not allowed').format(group_type))
1286
1287     def impl_get_group_type(self):
1288         return self._group_type
1289
1290     def _valid_consistency(self, option, value, context, index):
1291         if self._cache_consistencies is None:
1292             return True
1293         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1294         consistencies = self._cache_consistencies.get(option)
1295         if consistencies is not None:
1296             for func, all_cons_opts in consistencies:
1297                 #all_cons_opts[0] is the option where func is set
1298                 all_cons_opts[0]._launch_consistency(func, option,
1299                                                      value,
1300                                                      context, index,
1301                                                      all_cons_opts)
1302
1303     def _impl_getstate(self, descr=None):
1304         """enables us to export into a dict
1305         :param descr: parent :class:`tiramisu.option.OptionDescription`
1306         """
1307         if descr is None:
1308             self.impl_build_cache()
1309             descr = self
1310         super(OptionDescription, self)._impl_getstate(descr)
1311         self._state_group_type = str(self._group_type)
1312         for option in self.impl_getchildren():
1313             option._impl_getstate(descr)
1314
1315     def __getstate__(self):
1316         """special method to enable the serialization with pickle
1317         """
1318         stated = True
1319         try:
1320             # the `_state` attribute is a flag that which tells us if
1321             # the serialization can be performed
1322             self._stated
1323         except AttributeError:
1324             # if cannot delete, _impl_getstate never launch
1325             # launch it recursivement
1326             # _stated prevent __getstate__ launch more than one time
1327             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1328             self._impl_getstate()
1329             stated = False
1330         return super(OptionDescription, self).__getstate__(stated)
1331
1332     def _impl_setstate(self, descr=None):
1333         """enables us to import from a dict
1334         :param descr: parent :class:`tiramisu.option.OptionDescription`
1335         """
1336         if descr is None:
1337             self._cache_paths = None
1338             self._cache_consistencies = None
1339             self.impl_build_cache(force_no_consistencies=True)
1340             descr = self
1341         self._group_type = getattr(groups, self._state_group_type)
1342         del(self._state_group_type)
1343         super(OptionDescription, self)._impl_setstate(descr)
1344         for option in self.impl_getchildren():
1345             option._impl_setstate(descr)
1346
1347     def __setstate__(self, state):
1348         super(OptionDescription, self).__setstate__(state)
1349         try:
1350             self._stated
1351         except AttributeError:
1352             self._impl_setstate()
1353
1354
1355 def validate_requires_arg(requires, name):
1356     """check malformed requirements
1357     and tranform dict to internal tuple
1358
1359     :param requires: have a look at the
1360                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1361                      know more about
1362                      the description of the requires dictionary
1363     """
1364     if requires is None:
1365         return None, None
1366     ret_requires = {}
1367     config_action = {}
1368
1369     # start parsing all requires given by user (has dict)
1370     # transforme it to a tuple
1371     for require in requires:
1372         if not type(require) == dict:
1373             raise ValueError(_("malformed requirements type for option:"
1374                                " {0}, must be a dict").format(name))
1375         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1376                       'same_action')
1377         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1378         if unknown_keys != frozenset():
1379             raise ValueError('malformed requirements for option: {0}'
1380                              ' unknown keys {1}, must only '
1381                              '{2}'.format(name,
1382                                           unknown_keys,
1383                                           valid_keys))
1384         # prepare all attributes
1385         try:
1386             option = require['option']
1387             expected = require['expected']
1388             action = require['action']
1389         except KeyError:
1390             raise ValueError(_("malformed requirements for option: {0}"
1391                                " require must have option, expected and"
1392                                " action keys").format(name))
1393         inverse = require.get('inverse', False)
1394         if inverse not in [True, False]:
1395             raise ValueError(_('malformed requirements for option: {0}'
1396                                ' inverse must be boolean'))
1397         transitive = require.get('transitive', True)
1398         if transitive not in [True, False]:
1399             raise ValueError(_('malformed requirements for option: {0}'
1400                                ' transitive must be boolean'))
1401         same_action = require.get('same_action', True)
1402         if same_action not in [True, False]:
1403             raise ValueError(_('malformed requirements for option: {0}'
1404                                ' same_action must be boolean'))
1405
1406         if not isinstance(option, Option):
1407             raise ValueError(_('malformed requirements '
1408                                'must be an option in option {0}').format(name))
1409         if option.impl_is_multi():
1410             raise ValueError(_('malformed requirements option {0} '
1411                                'should not be a multi').format(name))
1412         if expected is not None:
1413             try:
1414                 option._validate(expected)
1415             except ValueError as err:
1416                 raise ValueError(_('malformed requirements second argument '
1417                                    'must be valid for option {0}'
1418                                    ': {1}').format(name, err))
1419         if action in config_action:
1420             if inverse != config_action[action]:
1421                 raise ValueError(_("inconsistency in action types"
1422                                    " for option: {0}"
1423                                    " action: {1}").format(name, action))
1424         else:
1425             config_action[action] = inverse
1426         if action not in ret_requires:
1427             ret_requires[action] = {}
1428         if option not in ret_requires[action]:
1429             ret_requires[action][option] = (option, [expected], action,
1430                                             inverse, transitive, same_action)
1431         else:
1432             ret_requires[action][option][1].append(expected)
1433     # transform dict to tuple
1434     ret = []
1435     for opt_requires in ret_requires.values():
1436         ret_action = []
1437         for require in opt_requires.values():
1438             ret_action.append((require[0], tuple(require[1]), require[2],
1439                                require[3], require[4], require[5]))
1440         ret.append(tuple(ret_action))
1441     return frozenset(config_action.keys()), tuple(ret)
1442
1443
1444 def validate_callback(callback, callback_params, type_):
1445     if type(callback) != FunctionType:
1446         raise ValueError(_('{0} should be a function').format(type_))
1447     if callback_params is not None:
1448         if not isinstance(callback_params, dict):
1449             raise ValueError(_('{0}_params should be a dict').format(type_))
1450         for key, callbacks in callback_params.items():
1451             if key != '' and len(callbacks) != 1:
1452                 raise ValueError(_('{0}_params with key {1} should not have '
1453                                    'length different to 1').format(type_,
1454                                                                    key))
1455             if not isinstance(callbacks, tuple):
1456                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1457                                    ).format(type_, key))
1458             for callbk in callbacks:
1459                 if isinstance(callbk, tuple):
1460                     option, force_permissive = callbk
1461                     if type_ == 'validator' and not force_permissive:
1462                         raise ValueError(_('validator not support tuple'))
1463                     if not isinstance(option, Option) and not \
1464                             isinstance(option, SymLinkOption):
1465                         raise ValueError(_('{0}_params should have an option '
1466                                            'not a {0} for first argument'
1467                                            ).format(type_, type(option)))
1468                     if force_permissive not in [True, False]:
1469                         raise ValueError(_('{0}_params should have a boolean'
1470                                            ' not a {0} for second argument'
1471                                            ).format(type_, type(
1472                                                force_permissive)))