Merge branch 'master' into orm
[tiramisu.git] / tiramisu / option.py
index 409d6eb..66f2a38 100644 (file)
@@ -22,7 +22,7 @@
 # ____________________________________________________________
 import re
 import sys
-from copy import copy, deepcopy
+from copy import copy
 from types import FunctionType
 from IPy import IP
 import warnings
@@ -32,6 +32,9 @@ from tiramisu.setting import groups, multitypes
 from tiramisu.i18n import _
 from tiramisu.autolib import carry_out_calculation
 
+#FIXME : need storage...
+from tiramisu.storage.sqlalchemy.option import StorageBase, StorageOptionDescription
+
 name_regexp = re.compile(r'^\d+')
 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
                    'make_dict', 'unwrap_from_path', 'read_only',
@@ -53,23 +56,42 @@ def valid_name(name):
 #
 
 
-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',
-                 '_calc_properties', '_impl_informations',
-                 '_state_readonly', '_state_requires', '_stated')
-
-    def __init__(self, name, doc, requires, properties):
+class Base(StorageBase):
+    def __init__(self, name, doc, default=None, default_multi=None,
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, warnings_only=False, choice_values=None,
+                 choice_open_values=None):
         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)
+        if requires is not None:
+            self._calc_properties, self._requires = validate_requires_arg(
+                requires, self._name)
+        if not multi and default_multi is not None:
+            raise ValueError(_("a default_multi is set whereas multi is False"
+                             " in option: {0}").format(name))
+        if default_multi is not None:
+            try:
+                self._validate(default_multi)
+            except ValueError as err:
+                raise ValueError(_("invalid default_multi value {0} "
+                                   "for option {1}: {2}").format(
+                                       str(default_multi), name, err))
+        self._multi = multi
+        if self._multi:
+            if default is None:
+                default = []
+            self._multitype = multitypes.default
+            self._default_multi = default_multi
+        if callback is not None and ((not multi and (default is not None or
+                                                     default_multi is not None))
+                                     or (multi and (default != [] or
+                                                    default_multi is not None))
+                                     ):
+            raise ValueError(_("default value not allowed if option: {0} "
+                             "is calculated").format(name))
         if properties is None:
             properties = tuple()
         if not isinstance(properties, tuple):
@@ -77,46 +99,50 @@ class BaseOption(object):
                             ' 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 validator is not None:
+            validate_callback(validator, validator_params, 'validator')
+            self._validator = validator
+            if validator_params is not None:
+                self._validator_params = validator_params
+        if callback is None and callback_params is not None:
+            raise ValueError(_("params defined for a callback function but "
+                             "no callback defined"
+                             " yet for option {0}").format(name))
+        if callback is not None:
+            validate_callback(callback, callback_params, 'callback')
+            self._callback = callback
+            if callback_params is not None:
+                self._callback_params = callback_params
+        if self._calc_properties != frozenset([]) and properties is not tuple():
+            set_forbidden_properties = self._calc_properties & set(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'...
+        if choice_values is not None:
+            self._choice_values = choice_values
+        if choice_open_values is not None:
+            self._choice_open_values = choice_open_values
+        self.impl_validate(default)
+        if multi and default is None:
+            self._default = []
+        else:
+            self._default = default
+        self._properties = properties
+        #for prop in properties:
+            #self._properties.append(self._get_property_object(prop))
+        self._warnings_only = warnings_only
+        return super(Base, self).__init__()
 
-    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 not name.startswith('_cache'):
-            is_readonly = False
-            # never change _name
-            if name == '_name':
-                try:
-                    self._name
-                    #so _name is already set
-                    is_readonly = True
-                except:
-                    pass
-            elif name != '_readonly':
-                try:
-                    if self._readonly is True:
-                        is_readonly = True
-                except AttributeError:
-                    self._readonly = False
-            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)
+class BaseOption(Base):
+    """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',
+    #             '_calc_properties', '_impl_informations',
+    #             '_state_readonly', '_state_requires', '_stated')
 
     # information
     def impl_set_information(self, key, value):
@@ -126,21 +152,23 @@ class BaseOption(object):
         :param key: information's key (ex: "help", "doc"
         :param value: information's value (ex: "the help string")
         """
-        self._impl_informations[key] = value
+        self._informations[key] = value
 
     def impl_get_information(self, key, default=None):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
-        if key in self._impl_informations:
-            return self._impl_informations[key]
+        if key in self._informations:
+            return self._informations[key]
         elif default is not None:
             return default
         else:
             raise ValueError(_("information's item not found: {0}").format(
                 key))
 
+    # ____________________________________________________________
+    # serialize object
     def _impl_convert_requires(self, descr, load=False):
         """export of the requires during the serialization process
 
@@ -257,23 +285,31 @@ class BaseOption(object):
         for key, value in state.items():
             setattr(self, key, value)
 
+    def impl_getname(self):
+        return self._name
+
 
-class Option(BaseOption):
+class OnlyOption(BaseOption):
+    pass
+
+
+class Option(OnlyOption):
     """
     Abstract base class for configuration option's.
 
     Reminder: an Option object is **not** a container for the value.
     """
-    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
-                 '_state_callback', '_callback', '_multitype',
-                 '_consistencies', '_warnings_only', '_master_slaves',
-                 '_state_consistencies', '__weakref__')
+#    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
+#                 '_state_callback', '_callback', '_multitype',
+#                 '_consistencies', '_warnings_only', '_master_slaves',
+#                 '_state_consistencies', '__weakref__')
     _empty = ''
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
                  callback_params=None, validator=None, validator_params=None,
-                 properties=None, warnings_only=False):
+                 properties=None, warnings_only=False, choice_values=None,
+                 choice_open_values=None):
         """
         :param name: the option's name
         :param doc: the option's description
@@ -295,45 +331,55 @@ class Option(BaseOption):
                              Values()._warning contain message
 
         """
-        super(Option, self).__init__(name, doc, requires, properties)
-        self._multi = multi
-        if validator is not None:
-            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:
-            raise ValueError(_("a default_multi is set whereas multi is False"
-                             " in option: {0}").format(name))
-        if default_multi is not None:
-            try:
-                self._validate(default_multi)
-            except ValueError as err:
-                raise ValueError(_("invalid default_multi value {0} "
-                                   "for option {1}: {2}").format(
-                                       str(default_multi), name, err))
-        if callback is not None and (default is not None or
-                                     default_multi is not None):
-            raise ValueError(_("default value not allowed if option: {0} "
-                             "is calculated").format(name))
-        if callback is None and callback_params is not None:
-            raise ValueError(_("params defined for a callback function but "
-                             "no callback defined"
-                             " yet for option {0}").format(name))
-        if callback is not None:
-            validate_callback(callback, callback_params, 'callback')
-            self._callback = (callback, callback_params)
-        else:
-            self._callback = None
-        if self._multi:
-            if default is None:
-                default = []
-            self._multitype = multitypes.default
-            self._default_multi = default_multi
-        self._warnings_only = warnings_only
-        self.impl_validate(default)
-        self._default = default
-        self._consistencies = None
+        super(Option, self).__init__(name, doc, default, default_multi,
+                                     requires, multi, callback,
+                                     callback_params, validator,
+                                     validator_params, properties,
+                                     warnings_only, choice_values,
+                                     choice_open_values)
+
+    def impl_is_readonly(self):
+        try:
+            if self._readonly is True:
+                return True
+        except AttributeError:
+            pass
+        return False
+
+    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)
+        """
+        #FIXME ne devrait pas pouvoir redefinir _option
+        #FIXME c'est une merde pour sqlachemy surement un cache ...
+        if not name == '_option' and not isinstance(value, tuple):
+            is_readonly = False
+            # never change _name
+            if name == '_name':
+                try:
+                    if self._name is not None:
+                        #so _name is already set
+                        is_readonly = True
+                #FIXME je n'aime pas ce except ...
+                except KeyError:
+                    pass
+            elif name != '_readonly':
+                is_readonly = self.impl_is_readonly()
+            if is_readonly:
+                raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
+                                       " read-only").format(
+                                           self.__class__.__name__,
+                                           self._name,
+                                           name))
+        super(Option, self).__setattr__(name, value)
+
+    def impl_getrequires(self):
+        return self._requires
 
     def _launch_consistency(self, func, option, value, context, index,
                             all_cons_opts):
@@ -400,8 +446,11 @@ class Option(BaseOption):
 
         def val_validator(val):
             if self._validator is not None:
-                if self._validator[1] is not None:
-                    validator_params = deepcopy(self._validator[1])
+                if self._validator_params is not None:
+                    validator_params = {}
+                    for val_param, values in self._validator_params.items():
+                        validator_params[val_param] = values
+                    #FIXME ... ca sert √† quoi ...
                     if '' in validator_params:
                         lst = list(validator_params[''])
                         lst.insert(0, val)
@@ -412,7 +461,7 @@ class Option(BaseOption):
                     validator_params = {'': (val,)}
                 # Raise ValueError if not valid
                 carry_out_calculation(self, config=context,
-                                      callback=self._validator[0],
+                                      callback=self._validator,
                                       callback_params=validator_params)
 
         def do_validation(_value, _index=None):
@@ -423,7 +472,7 @@ class Option(BaseOption):
                 self._validate(_value)
             except ValueError as err:
                 raise ValueError(_('invalid value for option {0}: {1}'
-                                   '').format(self._name, err))
+                                   '').format(self.impl_getname(), err))
             try:
                 # valid with self._validator
                 val_validator(_value)
@@ -433,7 +482,7 @@ class Option(BaseOption):
                 self._second_level_validation(_value)
             except ValueError as err:
                 msg = _("invalid value for option {0}: {1}").format(
-                    self._name, err)
+                    self.impl_getname(), err)
                 if self._warnings_only:
                     warnings.warn_explicit(ValueWarning(msg, self),
                                            ValueWarning,
@@ -449,7 +498,7 @@ class Option(BaseOption):
             do_validation(value, force_index)
         else:
             if not isinstance(value, list):
-                raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name))
+                raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
             for index, val in enumerate(value):
                 do_validation(val, index)
 
@@ -486,8 +535,11 @@ class Option(BaseOption):
         else:
             return True
 
-    def impl_getkey(self, value):
-        return value
+    def impl_get_callback(self):
+        return self._callback, self._callback_params
+
+    #def impl_getkey(self, value):
+    #    return value
 
     def impl_is_multi(self):
         return self._multi
@@ -501,8 +553,11 @@ class Option(BaseOption):
         :param other_opts: options used to validate value
         :type other_opts: `list` of `tiramisu.option.Option`
         """
-        if self._consistencies is None:
-            self._consistencies = []
+        if self.impl_is_readonly():
+            raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
+                                   " read-only").format(
+                                       self.__class__.__name__,
+                                       self._name))
         for opt in other_opts:
             if not isinstance(opt, Option):
                 raise ConfigError(_('consistency should be set with an option'))
@@ -522,7 +577,7 @@ class Option(BaseOption):
             else:
                 self._launch_consistency(func, self, value, None,
                                          None, all_cons_opts)
-        self._consistencies.append((func, all_cons_opts))
+        self._add_consistency(func, all_cons_opts)
         self.impl_validate(self.impl_getdefault())
 
     def _cons_not_equal(self, opts, vals):
@@ -530,7 +585,7 @@ class Option(BaseOption):
             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
                 if val_inf == val_sup is not None:
                     raise ValueError(_("same value for {0} and {1}").format(
-                        opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
+                        opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
 
     def _impl_convert_callbacks(self, descr, load=False):
         if not load and self._callback is None:
@@ -627,9 +682,7 @@ class ChoiceOption(Option):
     The option can also have the value ``None``
     """
 
-    __slots__ = ('_values', '_open_values')
-    _opt_type = 'string'
-
+    #__slots__ = ('_values', '_open_values')
     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,
@@ -639,11 +692,9 @@ class ChoiceOption(Option):
         """
         if not isinstance(values, tuple):
             raise TypeError(_('values must be a tuple for {0}').format(name))
-        self._values = values
         if open_values not in (True, False):
             raise TypeError(_('open_values must be a boolean for '
                             '{0}').format(name))
-        self._open_values = open_values
         super(ChoiceOption, self).__init__(name, doc, default=default,
                                            default_multi=default_multi,
                                            callback=callback,
@@ -653,25 +704,26 @@ class ChoiceOption(Option):
                                            validator=validator,
                                            validator_params=validator_params,
                                            properties=properties,
-                                           warnings_only=warnings_only)
+                                           warnings_only=warnings_only,
+                                           choice_values=values,
+                                           choice_open_values=open_values)
 
     def impl_get_values(self):
-        return self._values
+        return self._choice_values
 
     def impl_is_openvalues(self):
-        return self._open_values
+        return self._choice_open_values
 
     def _validate(self, value):
         if not self.impl_is_openvalues() and not value in self.impl_get_values():
             raise ValueError(_('value {0} is not permitted, '
                                'only {1} is allowed'
-                               '').format(value, self._values))
+                               '').format(value, self._choice_values))
 
 
 class BoolOption(Option):
     "represents a choice between ``True`` and ``False``"
-    __slots__ = tuple()
-    _opt_type = 'bool'
+#    __slots__ = tuple()
 
     def _validate(self, value):
         if not isinstance(value, bool):
@@ -680,8 +732,7 @@ class BoolOption(Option):
 
 class IntOption(Option):
     "represents a choice of an integer"
-    __slots__ = tuple()
-    _opt_type = 'int'
+#    __slots__ = tuple()
 
     def _validate(self, value):
         if not isinstance(value, int):
@@ -690,8 +741,7 @@ class IntOption(Option):
 
 class FloatOption(Option):
     "represents a choice of a floating point number"
-    __slots__ = tuple()
-    _opt_type = 'float'
+    #__slots__ = tuple()
 
     def _validate(self, value):
         if not isinstance(value, float):
@@ -700,8 +750,7 @@ class FloatOption(Option):
 
 class StrOption(Option):
     "represents the choice of a string"
-    __slots__ = tuple()
-    _opt_type = 'string'
+    #__slots__ = tuple()
 
     def _validate(self, value):
         if not isinstance(value, str):
@@ -711,13 +760,12 @@ class StrOption(Option):
 if sys.version_info[0] >= 3:
     #UnicodeOption is same as StrOption in python 3+
     class UnicodeOption(StrOption):
-        __slots__ = tuple()
+        #__slots__ = tuple()
         pass
 else:
     class UnicodeOption(Option):
         "represents the choice of a unicode string"
-        __slots__ = tuple()
-        _opt_type = 'unicode'
+        #__slots__ = tuple()
         _empty = u''
 
         def _validate(self, value):
@@ -725,11 +773,10 @@ else:
                 raise ValueError(_('invalid unicode'))
 
 
-class SymLinkOption(BaseOption):
-    __slots__ = ('_name', '_opt', '_state_opt')
-    _opt_type = 'symlink'
+class SymLinkOption(OnlyOption):
+    #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
     #not return _opt consistencies
-    _consistencies = None
+    #_consistencies = None
 
     def __init__(self, name, opt):
         self._name = name
@@ -739,9 +786,11 @@ class SymLinkOption(BaseOption):
                                'for symlink {0}').format(name))
         self._opt = opt
         self._readonly = True
+        self._parent = None
+        self.commit()
 
     def __getattr__(self, name):
-        if name in ('_name', '_opt', '_opt_type', '_readonly'):
+        if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
             return object.__getattr__(self, name)
         else:
             return getattr(self._opt, name)
@@ -755,12 +804,13 @@ class SymLinkOption(BaseOption):
         del(self._state_opt)
         super(SymLinkOption, self)._impl_setstate(descr)
 
+    def impl_get_information(self, key, default=None):
+        return self._opt.impl_get_information(key, default)
+
 
 class IPOption(Option):
     "represents the choice of an ip"
-    __slots__ = ('_private_only', '_allow_reserved')
-    _opt_type = 'ip'
-
+    #__slots__ = ('_private_only', '_allow_reserved')
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
                  callback_params=None, validator=None, validator_params=None,
@@ -813,18 +863,16 @@ class PortOption(Option):
     Port number 0 is reserved and can't be used.
     see: http://en.wikipedia.org/wiki/Port_numbers
     """
-    __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
-    _opt_type = 'port'
-
+    #__slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=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, warnings_only=False):
-        self._allow_range = allow_range
-        self._min_value = None
-        self._max_value = None
+        extra = {'_allow_range': allow_range,
+                 '_min_value': None,
+                 '_max_value': None}
         ports_min = [0, 1, 1024, 49152]
         ports_max = [0, 1023, 49151, 65535]
         is_finally = False
@@ -832,17 +880,17 @@ class PortOption(Option):
                                          allow_wellknown,
                                          allow_registred,
                                          allow_private]):
-            if self._min_value is None:
+            if extra['_min_value'] is None:
                 if allowed:
-                    self._min_value = ports_min[index]
+                    extra['_min_value'] = ports_min[index]
             elif not allowed:
                 is_finally = True
             elif allowed and is_finally:
                 raise ValueError(_('inconsistency in allowed range'))
             if allowed:
-                self._max_value = ports_max[index]
+                extra['_max_value'] = ports_max[index]
 
-        if self._max_value is None:
+        if extra['_max_value'] is None:
             raise ValueError(_('max value is empty'))
 
         super(PortOption, self).__init__(name, doc, default=default,
@@ -855,9 +903,10 @@ class PortOption(Option):
                                          validator_params=validator_params,
                                          properties=properties,
                                          warnings_only=warnings_only)
+        self._extra = extra
 
     def _validate(self, value):
-        if self._allow_range and ":" in str(value):
+        if self._extra['_allow_range'] and ":" in str(value):
             value = str(value).split(':')
             if len(value) != 2:
                 raise ValueError(_('invalid part, range must have two values '
@@ -870,19 +919,18 @@ class PortOption(Option):
 
         for val in value:
             try:
-                if not self._min_value <= int(val) <= self._max_value:
-                    raise ValueError(_('invalid port, must be an between {0} '
-                                     'and {1}').format(self._min_value,
-                                                      self._max_value))
+                if not self._extra['_min_value'] <= int(val) <= self._extra['_max_value']:
+                    raise ValueError('invalid port, must be an between {0} '
+                                     'and {1}'.format(
+                                         self._extra['_min_value'],
+                                         self._extra['_max_value']))
             except ValueError:
                 raise ValueError(_('invalid port'))
 
 
 class NetworkOption(Option):
     "represents the choice of a network"
-    __slots__ = tuple()
-    _opt_type = 'network'
-
+    #__slots__ = tuple()
     def _validate(self, value):
         try:
             IP(value)
@@ -897,8 +945,7 @@ class NetworkOption(Option):
 
 class NetmaskOption(Option):
     "represents the choice of a netmask"
-    __slots__ = tuple()
-    _opt_type = 'netmask'
+    #__slots__ = tuple()
 
     def _validate(self, value):
         try:
@@ -946,13 +993,12 @@ class NetmaskOption(Option):
             else:
                 msg = _('invalid network {0} ({1}) with netmask {2}')
         if msg is not None:
-            raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
+            raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
                                         val_netmask))
 
 
 class BroadcastOption(Option):
-    __slots__ = tuple()
-    _opt_type = 'broadcast'
+    #__slots__ = tuple()
 
     def _validate(self, value):
         try:
@@ -969,8 +1015,8 @@ class BroadcastOption(Option):
         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
                                '({3}) and netmask {4} ({5})').format(
-                                   broadcast, opts[0]._name, network,
-                                   opts[1]._name, netmask, opts[2]._name))
+                                   broadcast, opts[0].impl_getname(), network,
+                                   opts[1].impl_getname(), netmask, opts[2].impl_getname()))
 
 
 class DomainnameOption(Option):
@@ -980,8 +1026,7 @@ class DomainnameOption(Option):
     domainname:
     fqdn: with tld, not supported yet
     """
-    __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
-    _opt_type = 'domainname'
+    #__slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
@@ -990,7 +1035,7 @@ class DomainnameOption(Option):
                  warnings_only=False, allow_without_dot=False):
         if type_ not in ['netbios', 'hostname', 'domainname']:
             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
-        self._type = type_
+        self._dom_type = type_
         if allow_ip not in [True, False]:
             raise ValueError(_('allow_ip must be a boolean'))
         if allow_without_dot not in [True, False]:
@@ -1000,11 +1045,11 @@ class DomainnameOption(Option):
         end = ''
         extrachar = ''
         extrachar_mandatory = ''
-        if self._type == 'netbios':
+        if self._dom_type == 'netbios':
             length = 14
-        elif self._type == 'hostname':
+        elif self._dom_type == 'hostname':
             length = 62
-        elif self._type == 'domainname':
+        elif self._dom_type == 'domainname':
             length = 62
             if allow_without_dot is False:
                 extrachar_mandatory = '\.'
@@ -1031,7 +1076,7 @@ class DomainnameOption(Option):
                 return
             except ValueError:
                 pass
-        if self._type == 'domainname' and not self._allow_without_dot and \
+        if self._dom_type == 'domainname' and not self._allow_without_dot and \
                 '.' not in value:
             raise ValueError(_("invalid domainname, must have dot"))
             if len(value) > 255:
@@ -1043,8 +1088,7 @@ class DomainnameOption(Option):
 
 
 class EmailOption(DomainnameOption):
-    __slots__ = tuple()
-    _opt_type = 'email'
+    #__slots__ = tuple()
     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
 
     def _validate(self, value):
@@ -1060,8 +1104,7 @@ class EmailOption(DomainnameOption):
 
 
 class URLOption(DomainnameOption):
-    __slots__ = tuple()
-    _opt_type = 'url'
+    #__slots__ = tuple()
     proto_re = re.compile(r'(http|https)://')
     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
 
@@ -1097,8 +1140,7 @@ class URLOption(DomainnameOption):
 
 
 class FilenameOption(Option):
-    __slots__ = tuple()
-    _opt_type = 'file'
+    #__slots__ = tuple()
     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
 
     def _validate(self, value):
@@ -1107,24 +1149,23 @@ class FilenameOption(Option):
             raise ValueError(_('invalid filename'))
 
 
-class OptionDescription(BaseOption):
+class OptionDescription(BaseOption, StorageOptionDescription):
     """Config's schema (organisation, group) and container of Options
     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
     """
-    __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
-                 '_state_group_type', '_properties', '_children',
-                 '_cache_consistencies', '_calc_properties', '__weakref__',
-                 '_readonly', '_impl_informations', '_state_requires',
-                 '_stated', '_state_readonly')
-    _opt_type = 'optiondescription'
+    #_slots = ('_name', '_requires', '_cache_paths', '_group_type',
+    #          '_state_group_type', '_properties', '_children',
+    #          '_cache_consistencies', '_calc_properties', '__weakref__',
+    #          '_readonly', '_impl_informations', '_state_requires',
+    #          '_stated', '_state_readonly')
 
     def __init__(self, name, doc, children, requires=None, properties=None):
         """
         :param children: a list of options (including optiondescriptions)
 
         """
-        super(OptionDescription, self).__init__(name, doc, requires, properties)
-        child_names = [child._name for child in children]
+        super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
+        child_names = [child.impl_getname() for child in children]
         #better performance like this
         valid_child = copy(child_names)
         valid_child.sort()
@@ -1134,28 +1175,26 @@ class OptionDescription(BaseOption):
                 raise ConflictError(_('duplicate option name: '
                                       '{0}').format(child))
             old = child
-        self._children = (tuple(child_names), tuple(children))
-        self._cache_paths = None
+        for child in children:
+            if child._parent is not None:
+                raise ConflictError(_('duplicate option: '
+                                      '{0}').format(child))
+            self._children.append(child)  # = (tuple(child_names), tuple(children))
+        #FIXME pour dico !
+        #self._cache_paths = None
         self._cache_consistencies = None
         # the group_type is useful for filtering OptionDescriptions in a config
         self._group_type = groups.default
 
+    def impl_getrequires(self):
+        return self._requires
+
     def impl_getdoc(self):
         return self.impl_get_information('doc')
 
-    def __getattr__(self, name):
-        if name in self.__slots__:
-            return object.__getattribute__(self, name)
-        try:
-            return self._children[1][self._children[0].index(name)]
-        except ValueError:
-            raise AttributeError(_('unknown Option {0} '
-                                   'in OptionDescription {1}'
-                                   '').format(name, self._name))
-
-    def impl_getkey(self, config):
-        return tuple([child.impl_getkey(getattr(config, child._name))
-                      for child in self.impl_getchildren()])
+    def impl_validate(self, *args):
+        """usefull for OptionDescription"""
+        pass
 
     def impl_getpaths(self, include_groups=False, _currpath=None):
         """returns a list of all paths in self, recursively
@@ -1165,7 +1204,7 @@ class OptionDescription(BaseOption):
             _currpath = []
         paths = []
         for option in self.impl_getchildren():
-            attr = option._name
+            attr = option.impl_getname()
             if isinstance(option, OptionDescription):
                 if include_groups:
                     paths.append('.'.join(_currpath + [attr]))
@@ -1176,75 +1215,63 @@ class OptionDescription(BaseOption):
         return paths
 
     def impl_getchildren(self):
-        return self._children[1]
-
-    def impl_build_cache(self,
-                         cache_path=None,
-                         cache_option=None,
-                         _currpath=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 = []
-            if not force_no_consistencies:
-                _consistencies = {}
-        else:
-            save = False
-        if cache_path is None:
-            cache_path = []
+        #FIXME dans la base ??
+        return self._children
+        #for child in self._children:
+        #    yield(session.query(child._type).filter_by(id=child.id).first())
+
+    def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
+        #FIXME cache_option !
+        if _consistencies is None:
+            init = True
+            _consistencies = {}
             cache_option = []
+        else:
+            init = False
         for option in self.impl_getchildren():
-            attr = option._name
-            if option in cache_option:
-                raise ConflictError(_('duplicate option: {0}').format(option))
-
-            cache_option.append(option)
-            if not force_no_consistencies:
-                option._readonly = True
-            cache_path.append(str('.'.join(_currpath + [attr])))
+            cache_option.append(option.id)
             if not isinstance(option, OptionDescription):
-                if not force_no_consistencies and \
-                        option._consistencies is not None:
-                    for consistency in option._consistencies:
-                        func, all_cons_opts = consistency
-                        for opt in all_cons_opts:
-                            _consistencies.setdefault(opt,
-                                                      []).append((func,
-                                                                  all_cons_opts))
+                for consistency in option._consistencies:
+                    func = consistency.func
+                    all_cons_opts = consistency.options
+                    for opt in all_cons_opts:
+                        _consistencies.setdefault(opt,
+                                                  []).append((func,
+                                                             all_cons_opts))
             else:
-                _currpath.append(attr)
-                option.impl_build_cache(cache_path,
-                                        cache_option,
-                                        _currpath,
-                                        _consistencies,
-                                        force_no_consistencies)
-                _currpath.pop()
-        if save:
-            self._cache_paths = (tuple(cache_option), tuple(cache_path))
-            if not force_no_consistencies:
-                if _consistencies != {}:
-                    self._cache_consistencies = {}
-                    for opt, cons in _consistencies.items():
-                        if opt not in cache_option:
-                            raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
-                        self._cache_consistencies[opt] = tuple(cons)
-                self._readonly = True
-
-    def impl_get_opt_by_path(self, path):
-        try:
-            return self._cache_paths[0][self._cache_paths[1].index(path)]
-        except ValueError:
-            raise AttributeError(_('no option for path {0}').format(path))
-
-    def impl_get_path_by_opt(self, opt):
-        try:
-            return self._cache_paths[1][self._cache_paths[0].index(opt)]
-        except ValueError:
-            raise AttributeError(_('no option {0} found').format(opt))
+                option.impl_build_cache_consistency(_consistencies, cache_option)
+        if init and _consistencies != {}:
+            self._cache_consistencies = {}
+            for opt, cons in _consistencies.items():
+                #FIXME dans le cache ...
+                if opt.id not in cache_option:
+                    raise ConfigError(_('consistency with option {0} '
+                                        'which is not in Config').format(
+                                            opt.impl_getname()))
+                self._cache_consistencies[opt] = tuple(cons)
+
+    def impl_validate_options(self, cache_option=None):
+        """validate duplicate option and set option has readonly option
+        """
+        if cache_option is None:
+            init = True
+            cache_option = []
+        else:
+            init = False
+        for option in self.impl_getchildren():
+            #FIXME specifique id for sqlalchemy?
+            #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs diff√©rentes)
+            if option.id is None:
+                raise SystemError(_("an option's id should not be None "
+                                    "for {0}").format(option.impl_getname()))
+            if option.id in cache_option:
+                raise ConflictError(_('duplicate option: {0}').format(option))
+            cache_option.append(option.id)
+            option._readonly = True
+            if isinstance(option, OptionDescription):
+                option.impl_validate_options(cache_option)
+        if init:
+            self._readonly = True
 
     # ____________________________________________________________
     def impl_set_group_type(self, group_type):
@@ -1267,16 +1294,16 @@ class OptionDescription(BaseOption):
                 for child in self.impl_getchildren():
                     if isinstance(child, OptionDescription):
                         raise ValueError(_("master group {0} shall not have "
-                                         "a subgroup").format(self._name))
+                                         "a subgroup").format(self.impl_getname()))
                     if isinstance(child, SymLinkOption):
                         raise ValueError(_("master group {0} shall not have "
-                                         "a symlinkoption").format(self._name))
+                                         "a symlinkoption").format(self.impl_getname()))
                     if not child.impl_is_multi():
                         raise ValueError(_("not allowed option {0} "
                                          "in group {1}"
                                          ": this option is not a multi"
-                                         "").format(child._name, self._name))
-                    if child._name == self._name:
+                                         "").format(child.impl_getname(), self.impl_getname()))
+                    if child._name == self.impl_getname():
                         child._multitype = multitypes.master
                         master = child
                     else:
@@ -1284,9 +1311,10 @@ class OptionDescription(BaseOption):
                 if master is None:
                     raise ValueError(_('master group with wrong'
                                        ' master name for {0}'
-                                       ).format(self._name))
-                if master._callback is not None and master._callback[1] is not None:
-                    for key, callbacks in master._callback[1].items():
+                                       ).format(self.impl_getname()))
+                master_callback, master_callback_params = master.impl_get_callback()
+                if master_callback is not None and master_callback_params is not None:
+                    for key, callbacks in master_callback_params.items():
                         for callbk in callbacks:
                             if isinstance(callbk, tuple):
                                 if callbk[0] in slaves:
@@ -1301,9 +1329,6 @@ class OptionDescription(BaseOption):
             raise ValueError(_('group_type: {0}'
                                ' not allowed').format(group_type))
 
-    def impl_get_group_type(self):
-        return self._group_type
-
     def _valid_consistency(self, option, value, context, index):
         if self._cache_consistencies is None:
             return True
@@ -1320,6 +1345,9 @@ class OptionDescription(BaseOption):
                     return False
         return True
 
+    # ____________________________________________________________
+    # serialize object
+
     def _impl_getstate(self, descr=None):
         """enables us to export into a dict
         :param descr: parent :class:`tiramisu.option.OptionDescription`