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