serialize new callback
[tiramisu.git] / tiramisu / option.py
index 9648877..89d960a 100644 (file)
@@ -26,7 +26,7 @@ from copy import copy, deepcopy
 from types import FunctionType
 from IPy import IP
 
-from tiramisu.error import ConflictError
+from tiramisu.error import ConflictError, ConfigError
 from tiramisu.setting import groups, multitypes
 from tiramisu.i18n import _
 from tiramisu.autolib import carry_out_calculation
@@ -54,10 +54,78 @@ def valid_name(name):
 #
 
 
-class BaseInformation(object):
-    "interface for an option's information attribute"
-    __slots__ = ('_impl_informations',)
+class BaseOption(object):
+    """This abstract base class stands for attribute access
+    in options that have to be set only once, it is of course done in the
+    __setattr__ method
+    """
+    __slots__ = ('_name', '_requires', '_properties', '_readonly',
+                 '_consistencies', '_calc_properties', '_impl_informations',
+                 '_state_consistencies', '_state_readonly', '_state_requires',
+                 '_stated')
+
+    def __init__(self, name, doc, requires, properties):
+        if not valid_name(name):
+            raise ValueError(_("invalid name: {0} for option").format(name))
+        self._name = name
+        self._impl_informations = {}
+        self.impl_set_information('doc', doc)
+        self._calc_properties, self._requires = validate_requires_arg(
+            requires, self._name)
+        self._consistencies = None
+        if properties is None:
+            properties = tuple()
+        if not isinstance(properties, tuple):
+            raise TypeError(_('invalid properties type {0} for {1},'
+                            ' must be a tuple').format(
+                                type(properties),
+                                self._name))
+        if self._calc_properties is not None and properties is not tuple():
+            set_forbidden_properties = set(properties) & self._calc_properties
+            if set_forbidden_properties != frozenset():
+                raise ValueError('conflict: properties already set in '
+                                 'requirement {0}'.format(
+                                     list(set_forbidden_properties)))
+        self._properties = properties  # 'hidden', 'disabled'...
+
+    def __setattr__(self, name, value):
+        """set once and only once some attributes in the option,
+        like `_name`. `_name` cannot be changed one the option and
+        pushed in the :class:`tiramisu.option.OptionDescription`.
+
+        if the attribute `_readonly` is set to `True`, the option is
+        "frozen" (which has noting to do with the high level "freeze"
+        propertie or "read_only" property)
+        """
+        if not name.startswith('_state') and name not in ('_cache_paths',
+                                                          '_consistencies'):
+            is_readonly = False
+            # never change _name
+            if name == '_name':
+                try:
+                    self._name
+                    #so _name is already set
+                    is_readonly = True
+                except:
+                    pass
+            try:
+                if self._readonly is True:
+                    if value is True:
+                        # already readonly and try to re set readonly
+                        # don't raise, just exit
+                        return
+                    is_readonly = True
+            except AttributeError:
+                pass
+            if is_readonly:
+                raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
+                                       " read-only").format(
+                                           self.__class__.__name__,
+                                           self._name,
+                                           name))
+        object.__setattr__(self, name, value)
 
+    # information
     def impl_set_information(self, key, value):
         """updates the information's attribute
         (which is a dictionary)
@@ -65,138 +133,204 @@ class BaseInformation(object):
         :param key: information's key (ex: "help", "doc"
         :param value: information's value (ex: "the help string")
         """
-        try:
-            self._impl_informations[key] = value
-        except AttributeError:
-            raise AttributeError(_('{0} has no attribute '
-                                   'impl_set_information').format(
-                                       self.__class__.__name__))
+        self._impl_informations[key] = value
 
     def impl_get_information(self, key, default=None):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
-        try:
-            if key in self._impl_informations:
-                return self._impl_informations[key]
-            elif default is not None:
-                return default
+        if key in self._impl_informations:
+            return self._impl_informations[key]
+        elif default is not None:
+            return default
+        else:
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
+    # serialize/unserialize
+    def _impl_convert_consistencies(self, descr, load=False):
+        """during serialization process, many things have to be done.
+        one of them is the localisation of the options.
+        The paths are set once for all.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._consistencies is None:
+            self._state_consistencies = None
+        elif load and self._state_consistencies is None:
+            self._consistencies = None
+            del(self._state_consistencies)
+        else:
+            if load:
+                consistencies = self._state_consistencies
             else:
-                raise ValueError(_("information's item"
-                                   " not found: {0}").format(key))
-        except AttributeError:
-            raise AttributeError(_('{0} has no attribute '
-                                   'impl_get_information').format(
-                                       self.__class__.__name__))
+                consistencies = self._consistencies
+            if isinstance(consistencies, list):
+                new_value = []
+                for consistency in consistencies:
+                    if load:
+                        new_value.append((consistency[0],
+                                          descr.impl_get_opt_by_path(
+                                              consistency[1])))
+                    else:
+                        new_value.append((consistency[0],
+                                          descr.impl_get_path_by_opt(
+                                              consistency[1])))
 
+            else:
+                new_value = {}
+                for key, _consistencies in consistencies.items():
+                    new_value[key] = []
+                    for key_cons, _cons in _consistencies:
+                        _list_cons = []
+                        for _con in _cons:
+                            if load:
+                                _list_cons.append(
+                                    descr.impl_get_opt_by_path(_con))
+                            else:
+                                _list_cons.append(
+                                    descr.impl_get_path_by_opt(_con))
+                        new_value[key].append((key_cons, tuple(_list_cons)))
+            if load:
+                del(self._state_consistencies)
+                self._consistencies = new_value
+            else:
+                self._state_consistencies = new_value
 
-class BaseOption(BaseInformation):
-    """This abstract base class stands for attribute access
-    in options that have to be set only once, it is of course done in the
-    __setattr__ method
-    """
-    __slots__ = ('_readonly',)
+    def _impl_convert_requires(self, descr, load=False):
+        """export of the requires during the serialization process
 
-    def __setattr__(self, name, value):
-        """set once and only once some attributes in the option,
-        like `_name`. `_name` cannot be changed one the option and
-        pushed in the :class:`tiramisu.option.OptionDescription`.
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._requires is None:
+            self._state_requires = None
+        elif load and self._state_requires is None:
+            self._requires = None
+            del(self._state_requires)
+        else:
+            if load:
+                _requires = self._state_requires
+            else:
+                _requires = self._requires
+            new_value = []
+            for requires in _requires:
+                new_requires = []
+                for require in requires:
+                    if load:
+                        new_require = [descr.impl_get_opt_by_path(require[0])]
+                    else:
+                        new_require = [descr.impl_get_path_by_opt(require[0])]
+                    new_require.extend(require[1:])
+                    new_requires.append(tuple(new_require))
+                new_value.append(tuple(new_requires))
+            if load:
+                del(self._state_requires)
+                self._requires = new_value
+            else:
+                self._state_requires = new_value
 
-        if the attribute `_readonly` is set to `True`, the option is
-        "frozen" (which has noting to do with the high level "freeze"
-        propertie or "read_only" property)
+    # serialize
+    def _impl_getstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :param descr: the parent :class:`tiramisu.option.OptionDescription`
         """
-        is_readonly = False
-        # never change _name
-        if name == '_name':
-            try:
-                self._name
-                #so _name is already set
-                is_readonly = True
-            except AttributeError:
-                pass
+        self._stated = True
+        self._impl_convert_consistencies(descr)
+        self._impl_convert_requires(descr)
         try:
-            if self._readonly is True:
-                if value is True:
-                    # already readonly and try to re set readonly
-                    # don't raise, just exit
-                    return
-                is_readonly = True
+            self._state_readonly = self._readonly
         except AttributeError:
             pass
-        if is_readonly:
-            raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
-                                   " read-only").format(
-                                       self.__class__.__name__, self._name,
-                                       name))
-        object.__setattr__(self, name, value)
 
-    def _impl_convert_consistencies(self, value, cache):
-        # cache is a dico in import/not a dico in export
-        new_value = []
-        for consistency in value:
-            if isinstance(cache, dict):
-                new_value = (consistency[0], cache[consistency[1]])
-            else:
-                new_value = (consistency[0], cache.impl_get_path_by_opt(
-                    consistency[1]))
-        return tuple(new_value)
-
-    def _impl_convert_requires(self, value, cache):
-        # cache is a dico in import/not a dico in export
-        new_value = []
-        for requires in value:
-            new_requires = []
-            for require in requires:
-                if isinstance(cache, dict):
-                    new_require = [cache[require[0]]]
-                else:
-                    new_require = [cache.impl_get_path_by_opt(require[0])]
-                new_require.extend(require[1:])
-                new_requires.append(tuple(new_require))
-            new_value.append(tuple(new_requires))
-        return tuple(new_value)
-
-    def impl_export(self, descr):
-        descr.impl_build_cache()
-        # add _opt_type (not in __slots__)
-        slots = set(['_opt_type'])
+    def __getstate__(self, stated=True):
+        """special method to enable the serialization with pickle
+        Usualy, a `__getstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter stated: if stated is `True`, the serialization protocol
+                           can be performed, not ready yet otherwise
+        :parameter type: bool
+        """
+        try:
+            self._stated
+        except AttributeError:
+            raise SystemError(_('cannot serialize Option, '
+                                'only in OptionDescription'))
+        slots = set()
         for subclass in self.__class__.__mro__:
             if subclass is not object:
                 slots.update(subclass.__slots__)
-        slots -= frozenset(['_children', '_readonly', '_cache_paths',
-                            '__weakref__'])
-        exported_object = {}
-        for attr in slots:
-            try:
-                value = getattr(self, attr)
-                if value is not None:
-                    if attr == '_consistencies':
-                        value = self._impl_convert_consistencies(value, descr)
-                    elif attr == '_requires':
-                        value = self._impl_convert_requires(value, descr)
-                exported_object[attr] = value
-            except AttributeError:
-                pass
-        return exported_object
+        slots -= frozenset(['_cache_paths', '__weakref__'])
+        states = {}
+        for slot in slots:
+            # remove variable if save variable converted
+            # in _state_xxxx variable
+            if '_state' + slot not in slots:
+                if slot.startswith('_state'):
+                    # should exists
+                    states[slot] = getattr(self, slot)
+                    # remove _state_xxx variable
+                    self.__delattr__(slot)
+                else:
+                    try:
+                        states[slot] = getattr(self, slot)
+                    except AttributeError:
+                        pass
+        if not stated:
+            del(states['_stated'])
+        return states
+
+    # unserialize
+    def _impl_setstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        """
+        self._impl_convert_consistencies(descr, load=True)
+        self._impl_convert_requires(descr, load=True)
+        try:
+            self._readonly = self._state_readonly
+            del(self._state_readonly)
+            del(self._stated)
+        except AttributeError:
+            pass
+
+    def __setstate__(self, state):
+        """special method that enables us to serialize (pickle)
+
+        Usualy, a `__setstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter state: a dict is passed to the loads, it is the attributes
+                          of the options object
+        :type state: dict
+        """
+        for key, value in state.items():
+            setattr(self, key, value)
 
 
 class Option(BaseOption):
     """
     Abstract base class for configuration option's.
 
-    Reminder: an Option object is **not** a container for the value
+    Reminder: an Option object is **not** a container for the value.
     """
-    __slots__ = ('_name', '_requires', '_multi', '_validator',
-                 '_default_multi', '_default', '_properties', '_callback',
-                 '_multitype', '_master_slaves', '_consistencies',
-                 '_calc_properties', '__weakref__')
+    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
+                 '_state_callback', '_callback', '_multitype',
+                 '_master_slaves', '__weakref__')
     _empty = ''
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None):
         """
         :param name: the option's name
@@ -211,26 +345,17 @@ class Option(BaseOption):
         :param callback: the name of a function. If set, the function's output
                          is responsible of the option's value
         :param callback_params: the callback's parameter
-        :param validator: the name of a function wich stands for a custom
+        :param validator: the name of a function which stands for a custom
                           validation of the value
-        :param validator_args: the validator's parameters
+        :param validator_params: the validator's parameters
+        :param properties: tuple of default properties
 
         """
-        if not valid_name(name):
-            raise ValueError(_("invalid name: {0} for option").format(name))
-        self._name = name
-        self._impl_informations = {}
-        self.impl_set_information('doc', doc)
-        self._calc_properties, self._requires = validate_requires_arg(
-            requires, self._name)
+        super(Option, self).__init__(name, doc, requires, properties)
         self._multi = multi
-        self._consistencies = None
         if validator is not None:
-            if type(validator) != FunctionType:
-                raise TypeError(_("validator must be a function"))
-            if validator_args is None:
-                validator_args = {}
-            self._validator = (validator, validator_args)
+            validate_callback(validator, validator_params, 'validator')
+            self._validator = (validator, validator_params)
         else:
             self._validator = None
         if not self._multi and default_multi is not None:
@@ -252,11 +377,7 @@ class Option(BaseOption):
                              "no callback defined"
                              " yet for option {0}").format(name))
         if callback is not None:
-            if type(callback) != FunctionType:
-                raise ValueError('callback must be a function')
-            if callback_params is not None and \
-                    not isinstance(callback_params, dict):
-                raise ValueError('callback_params must be a dict')
+            validate_callback(callback, callback_params, 'callback')
             self._callback = (callback, callback_params)
         else:
             self._callback = None
@@ -267,14 +388,6 @@ class Option(BaseOption):
             self._default_multi = default_multi
         self.impl_validate(default)
         self._default = default
-        if properties is None:
-            properties = tuple()
-        if not isinstance(properties, tuple):
-            raise TypeError(_('invalid properties type {0} for {1},'
-                            ' must be a tuple').format(
-                                type(properties),
-                                self._name))
-        self._properties = properties  # 'hidden', 'disabled'...
 
     def _launch_consistency(self, func, opt, vals, context, index, opt_):
         if context is not None:
@@ -331,11 +444,23 @@ class Option(BaseOption):
 
         def val_validator(val):
             if self._validator is not None:
-                callback_params = deepcopy(self._validator[1])
-                callback_params.setdefault('', []).insert(0, val)
-                return carry_out_calculation(self._name, config=context,
-                                             callback=self._validator[0],
-                                             callback_params=callback_params)
+                if self._validator[1] is not None:
+                    validator_params = deepcopy(self._validator[1])
+                    if '' in validator_params:
+                        lst = list(validator_params[''])
+                        lst.insert(0, val)
+                        validator_params[''] = tuple(lst)
+                    else:
+                        validator_params[''] = (val,)
+                else:
+                    validator_params = {'': (val,)}
+                ret = carry_out_calculation(self._name, config=context,
+                                            callback=self._validator[0],
+                                            callback_params=validator_params)
+                if ret not in [False, True]:
+                    raise ConfigError(_('validator should return a boolean, '
+                                        'not {0}').format(ret))
+                return ret
             else:
                 return True
 
@@ -436,6 +561,57 @@ class Option(BaseOption):
                                "must be different as {2} option"
                                "").format(value, self._name, optname))
 
+    def _impl_convert_callbacks(self, descr, load=False):
+        if not load and self._callback is None:
+            self._state_callback = None
+        elif load and self._state_callback is None:
+            self._callback = None
+            del(self._state_callback)
+        else:
+            if load:
+                callback, callback_params = self._state_callback
+            else:
+                callback, callback_params = self._callback
+            if callback_params is not None:
+                cllbck_prms = {}
+                for key, values in callback_params.items():
+                    vls = []
+                    for value in values:
+                        if isinstance(value, tuple):
+                            if load:
+                                value = (descr.impl_get_opt_by_path(value[0]),
+                                         value[1])
+                            else:
+                                value = (descr.impl_get_path_by_opt(value[0]),
+                                         value[1])
+                        vls.append(value)
+                    cllbck_prms[key] = tuple(vls)
+            else:
+                cllbck_prms = None
+
+            if load:
+                del(self._state_callback)
+                self._callback = (callback, cllbck_prms)
+            else:
+                self._state_callback = (callback, cllbck_prms)
+
+    # serialize
+    def _impl_getstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+        """
+        self._stated = True
+        self._impl_convert_callbacks(descr)
+        super(Option, self)._impl_getstate(descr)
+
+    # unserialize
+    def _impl_setstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+        """
+        self._impl_convert_callbacks(descr, load=True)
+        super(Option, self)._impl_setstate(descr)
+
 
 class ChoiceOption(Option):
     """represents a choice out of several objects.
@@ -449,7 +625,7 @@ class ChoiceOption(Option):
     def __init__(self, name, doc, values, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
                  callback_params=None, open_values=False, validator=None,
-                 validator_args=None, properties=()):
+                 validator_params=None, properties=()):
         """
         :param values: is a list of values the option can possibly take
         """
@@ -467,7 +643,7 @@ class ChoiceOption(Option):
                                            requires=requires,
                                            multi=multi,
                                            validator=validator,
-                                           validator_args=validator_args,
+                                           validator_params=validator_params,
                                            properties=properties)
 
     def impl_get_values(self):
@@ -542,8 +718,10 @@ else:
 
 
 class SymLinkOption(BaseOption):
-    __slots__ = ('_name', '_opt')
+    __slots__ = ('_name', '_opt', '_state_opt')
     _opt_type = 'symlink'
+    #not return _opt consistencies
+    _consistencies = {}
 
     def __init__(self, name, opt):
         self._name = name
@@ -560,23 +738,33 @@ class SymLinkOption(BaseOption):
         else:
             return getattr(self._opt, name)
 
-    def impl_export(self, descr):
-        export = super(SymLinkOption, self).impl_export(descr)
-        export['_opt'] = descr.impl_get_path_by_opt(self._opt)
-        del(export['_impl_informations'])
-        return export
+    def _impl_getstate(self, descr):
+        super(SymLinkOption, self)._impl_getstate(descr)
+        self._state_opt = descr.impl_get_path_by_opt(self._opt)
+
+    def _impl_setstate(self, descr):
+        self._opt = descr.impl_get_opt_by_path(self._state_opt)
+        del(self._state_opt)
+        super(SymLinkOption, self)._impl_setstate(descr)
+
+    def _impl_convert_consistencies(self, descr, load=False):
+        if load:
+            del(self._state_consistencies)
+        else:
+            self._state_consistencies = None
 
 
 class IPOption(Option):
     "represents the choice of an ip"
-    __slots__ = ('_only_private',)
+    __slots__ = ('_only_private', '_allow_reserved')
     _opt_type = 'ip'
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
-                 properties=None, only_private=False):
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, only_private=False, allow_reserved=False):
         self._only_private = only_private
+        self._allow_reserved = allow_reserved
         super(IPOption, self).__init__(name, doc, default=default,
                                        default_multi=default_multi,
                                        callback=callback,
@@ -584,12 +772,12 @@ class IPOption(Option):
                                        requires=requires,
                                        multi=multi,
                                        validator=validator,
-                                       validator_args=validator_args,
+                                       validator_params=validator_params,
                                        properties=properties)
 
     def _validate(self, value):
         ip = IP('{0}/32'.format(value))
-        if ip.iptype() == 'RESERVED':
+        if not self._allow_reserved and ip.iptype() == 'RESERVED':
             raise ValueError(_("IP mustn't not be in reserved class"))
         if self._only_private and not ip.iptype() == 'PRIVATE':
             raise ValueError(_("IP must be in private class"))
@@ -610,7 +798,7 @@ class PortOption(Option):
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None, allow_range=False, allow_zero=False,
                  allow_wellknown=True, allow_registred=True,
                  allow_private=False):
@@ -644,7 +832,7 @@ class PortOption(Option):
                                          requires=requires,
                                          multi=multi,
                                          validator=validator,
-                                         validator_args=validator_args,
+                                         validator_params=validator_params,
                                          properties=properties)
 
     def _validate(self, value):
@@ -672,7 +860,7 @@ class NetworkOption(Option):
     def _validate(self, value):
         ip = IP(value)
         if ip.iptype() == 'RESERVED':
-            raise ValueError(_("network mustn't not be in reserved class"))
+            raise ValueError(_("network shall not be in reserved class"))
 
 
 class NetmaskOption(Option):
@@ -729,7 +917,7 @@ class DomainnameOption(Option):
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args=None,
+                 callback_params=None, validator=None, validator_params=None,
                  properties=None, allow_ip=False, type_='domainname'):
         #netbios: for MS domain
         #hostname: to identify the device
@@ -748,7 +936,7 @@ class DomainnameOption(Option):
                                                requires=requires,
                                                multi=multi,
                                                validator=validator,
-                                               validator_args=validator_args,
+                                               validator_params=validator_params,
                                                properties=properties)
 
     def _validate(self, value):
@@ -786,8 +974,10 @@ class OptionDescription(BaseOption):
     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
     """
     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
-                 '_properties', '_children', '_consistencies',
-                 '_calc_properties', '__weakref__', '_readonly', '_impl_informations')
+                 '_state_group_type', '_properties', '_children',
+                 '_consistencies', '_calc_properties', '__weakref__',
+                 '_readonly', '_impl_informations', '_state_requires',
+                 '_state_consistencies', '_stated', '_state_readonly')
     _opt_type = 'optiondescription'
 
     def __init__(self, name, doc, children, requires=None, properties=None):
@@ -795,12 +985,7 @@ class OptionDescription(BaseOption):
         :param children: a list of options (including optiondescriptions)
 
         """
-        if not valid_name(name):
-            raise ValueError(_("invalid name:"
-                               " {0} for optiondescription").format(name))
-        self._name = name
-        self._impl_informations = {}
-        self.impl_set_information('doc', doc)
+        super(OptionDescription, self).__init__(name, doc, requires, properties)
         child_names = [child._name for child in children]
         #better performance like this
         valid_child = copy(child_names)
@@ -812,16 +997,7 @@ class OptionDescription(BaseOption):
                                       '{0}').format(child))
             old = child
         self._children = (tuple(child_names), tuple(children))
-        self._calc_properties, self._requires = validate_requires_arg(requires, self._name)
         self._cache_paths = None
-        self._consistencies = None
-        if properties is None:
-            properties = tuple()
-        if not isinstance(properties, tuple):
-            raise TypeError(_('invalid properties type {0} for {1},'
-                              ' must be a tuple').format(type(properties),
-                                                         self._name))
-        self._properties = properties  # 'hidden', 'disabled'...
         # the group_type is useful for filtering OptionDescriptions in a config
         self._group_type = groups.default
 
@@ -867,14 +1043,16 @@ class OptionDescription(BaseOption):
                          cache_path=None,
                          cache_option=None,
                          _currpath=None,
-                         _consistencies=None):
+                         _consistencies=None,
+                         force_no_consistencies=False):
         if _currpath is None and self._cache_paths is not None:
             # cache already set
             return
         if _currpath is None:
             save = True
             _currpath = []
-            _consistencies = {}
+            if not force_no_consistencies:
+                _consistencies = {}
         else:
             save = False
         if cache_path is None:
@@ -886,10 +1064,12 @@ class OptionDescription(BaseOption):
                 raise ConflictError(_('duplicate option: {0}').format(option))
 
             cache_option.append(option)
-            option._readonly = True
+            if not force_no_consistencies:
+                option._readonly = True
             cache_path.append(str('.'.join(_currpath + [attr])))
             if not isinstance(option, OptionDescription):
-                if option._consistencies is not None:
+                if not force_no_consistencies and \
+                        option._consistencies is not None:
                     for consistency in option._consistencies:
                         func, opt = consistency
                         opts = (option, opt)
@@ -902,12 +1082,14 @@ class OptionDescription(BaseOption):
                 option.impl_build_cache(cache_path,
                                         cache_option,
                                         _currpath,
-                                        _consistencies)
+                                        _consistencies,
+                                        force_no_consistencies)
                 _currpath.pop()
         if save:
             self._cache_paths = (tuple(cache_option), tuple(cache_path))
-            self._consistencies = _consistencies
-            self._readonly = True
+            if not force_no_consistencies:
+                self._consistencies = _consistencies
+                self._readonly = True
 
     def impl_get_opt_by_path(self, path):
         try:
@@ -992,26 +1174,55 @@ class OptionDescription(BaseOption):
                     return False
         return True
 
-    def _impl_convert_group_type(self, value, cache):
-        if isinstance(cache, dict):
-            value = str(value)
-        else:
-            value = getattr(groups, value)
-        return value
-
-    def impl_export(self, descr=None):
+    def _impl_getstate(self, descr=None):
         """enables us to export into a dict
         :param descr: parent :class:`tiramisu.option.OptionDescription`
         """
         if descr is None:
+            self.impl_build_cache()
+            descr = self
+        super(OptionDescription, self)._impl_getstate(descr)
+        self._state_group_type = str(self._group_type)
+        for option in self.impl_getchildren():
+            option._impl_getstate(descr)
+
+    def __getstate__(self):
+        """special method to enable the serialization with pickle
+        """
+        stated = True
+        try:
+            # the `_state` attribute is a flag that which tells us if
+            # the serialization can be performed
+            self._stated
+        except AttributeError:
+            # if cannot delete, _impl_getstate never launch
+            # launch it recursivement
+            # _stated prevent __getstate__ launch more than one time
+            # _stated is delete, if re-serialize, re-lauch _impl_getstate
+            self._impl_getstate()
+            stated = False
+        return super(OptionDescription, self).__getstate__(stated)
+
+    def _impl_setstate(self, descr=None):
+        """enables us to import from a dict
+        :param descr: parent :class:`tiramisu.option.OptionDescription`
+        """
+        if descr is None:
+            self._cache_paths = None
+            self.impl_build_cache(force_no_consistencies=True)
             descr = self
-        export = super(OptionDescription, self).impl_export(descr)
-        export['_group_type'] = self._impl_convert_group_type(
-            export['_group_type'], descr)
-        export['options'] = []
+        self._group_type = getattr(groups, self._state_group_type)
+        del(self._state_group_type)
+        super(OptionDescription, self)._impl_setstate(descr)
         for option in self.impl_getchildren():
-            export['options'].append(option.impl_export(descr))
-        return export
+            option._impl_setstate(descr)
+
+    def __setstate__(self, state):
+        super(OptionDescription, self).__setstate__(state)
+        try:
+            self._stated
+        except AttributeError:
+            self._impl_setstate()
 
 
 def validate_requires_arg(requires, name):
@@ -1028,6 +1239,8 @@ def validate_requires_arg(requires, name):
     ret_requires = {}
     config_action = {}
 
+    # start parsing all requires given by user (has dict)
+    # transforme it to a tuple
     for require in requires:
         if not type(require) == dict:
             raise ValueError(_("malformed requirements type for option:"
@@ -1041,6 +1254,7 @@ def validate_requires_arg(requires, name):
                              '{2}'.format(name,
                                           unknown_keys,
                                           valid_keys))
+        # prepare all attributes
         try:
             option = require['option']
             expected = require['expected']
@@ -1089,12 +1303,43 @@ def validate_requires_arg(requires, name):
                                             inverse, transitive, same_action)
         else:
             ret_requires[action][option][1].append(expected)
+    # transform dict to tuple
     ret = []
     for opt_requires in ret_requires.values():
         ret_action = []
         for require in opt_requires.values():
-            req = (require[0], tuple(require[1]), require[2], require[3],
-                   require[4], require[5])
-            ret_action.append(req)
+            ret_action.append((require[0], tuple(require[1]), require[2],
+                               require[3], require[4], require[5]))
         ret.append(tuple(ret_action))
     return frozenset(config_action.keys()), tuple(ret)
+
+
+def validate_callback(callback, callback_params, type_):
+    if type(callback) != FunctionType:
+        raise ValueError(_('{0} should be a function').format(type_))
+    if callback_params is not None:
+        if not isinstance(callback_params, dict):
+            raise ValueError(_('{0}_params should be a dict').format(type_))
+        for key, callbacks in callback_params.items():
+            if key != '' and len(callbacks) != 1:
+                raise ValueError(_('{0}_params with key {1} should not have '
+                                   'length different to 1').format(type_,
+                                                                   key))
+            if not isinstance(callbacks, tuple):
+                raise ValueError(_('{0}_params should be tuple for key "{1}"'
+                                   ).format(type_, key))
+            for callbk in callbacks:
+                if isinstance(callbk, tuple):
+                    option, force_permissive = callbk
+                    if type_ == 'validator' and not force_permissive:
+                        raise ValueError(_('validator not support tuple'))
+                    if not isinstance(option, Option) and not \
+                            isinstance(option, SymLinkOption):
+                        raise ValueError(_('{0}_params should have an option '
+                                           'not a {0} for first argument'
+                                           ).format(type_, type(option)))
+                    if force_permissive not in [True, False]:
+                        raise ValueError(_('{0}_params should have a boolean'
+                                           'not a {0} for second argument'
+                                           ).format(type_, type(
+                                               force_permissive)))