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