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