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