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