Merge branch 'master' into orm
[tiramisu.git] / tiramisu / option.py
index ad99416..b4db8a8 100644 (file)
 # ____________________________________________________________
 import re
 import sys
-from copy import copy, deepcopy
+from copy import copy
 from types import FunctionType
 from IPy import IP
 import warnings
+#from pickle import loads, dumps
+
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
+    PickleType, ForeignKey, Table
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm import sessionmaker
 
 from tiramisu.error import ConfigError, ConflictError, ValueWarning
 from tiramisu.setting import groups, multitypes
@@ -52,24 +59,203 @@ def valid_name(name):
 #____________________________________________________________
 #
 
+engine = create_engine('sqlite:///:memory:')
+Base = declarative_base()
+
+
+class _RequireExpected(Base):
+    __tablename__ = 'expected'
+    id = Column(Integer, primary_key=True)
+    expected = Column(PickleType)
+    require = Column(Integer, ForeignKey('require.id'))
+
+    def __init__(self, expected):
+        self.expected = expected
+
+
+class _RequireOption(Base):
+    __tablename__ = 'require'
+    id = Column(Integer, primary_key=True)
+    option = Column(Integer, ForeignKey('baseoption.id'))
+    r_opt = Column(Integer)
+    expected = relationship("_RequireExpected")
+    action = Column(String, nullable=False)
+    inverse = Column(Boolean, default=False)
+    transitive = Column(Boolean, default=True)
+    same_action = Column(Boolean, default=True)
+
+    def __init__(self, option, expected, action, inverse, transitive,
+                 same_action):
+        self.r_opt = option.id
+        for expect in expected:
+            self.expected.append(_RequireExpected(expect))
+        self.action = action
+        self.inverse = inverse
+        self.transitive = transitive
+        self.same_action = same_action
+
+    def get_expected(self):
+        for expected in self.expected:
+            yield(expected.expected)
+
+    def get_option(self, config):
+        return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
+
+
+property_table = Table('property', Base.metadata,
+                       Column('left_id', Integer, ForeignKey('propertyoption.name')),
+                       Column('right_id', Integer, ForeignKey('baseoption.id'))
+                       )
+
+
+class _PropertyOption(Base):
+    __tablename__ = 'propertyoption'
+    name = Column(String, primary_key=True)
+
+    def __init__(self, name):
+        self.name = name
+
 
-class BaseOption(object):
+class _Information(Base):
+    __tablename__ = 'information'
+    id = Column(Integer, primary_key=True)
+    option = Column(Integer, ForeignKey('baseoption.id'))
+    key = Column(String)
+    value = Column(PickleType)
+
+    def __init__(self, key, value):
+        self.key = key
+        self.value = value
+
+
+class _CallbackParamOption(Base):
+    __tablename__ = 'callback_param_option'
+    id = Column(Integer, primary_key=True)
+    callback_param = Column(Integer, ForeignKey('callback_param.id'))
+    option = Column(Integer)
+    force_permissive = Column(Boolean)
+    value = Column(PickleType)
+
+    def __init__(self, option=None, force_permissive=None,  value=None):
+        if value is not None:
+            self.value = value
+        else:
+            if isinstance(option, SymLinkOption):
+                option = option._opt
+            self.option = option.id
+            self.force_permissive = force_permissive
+
+    def get_option(self, config):
+        return config.cfgimpl_get_description().impl_get_opt_by_id(self.option)
+
+
+class _CallbackParam(Base):
+    __tablename__ = 'callback_param'
+    id = Column(Integer, primary_key=True)
+    callback = Column(Integer, ForeignKey('baseoption.id'))
+    name = Column(String)
+    params = relationship('_CallbackParamOption')
+
+    def __init__(self, name, params):
+        self.name = name
+        for param in params:
+            if isinstance(param, tuple):
+                self.params.append(_CallbackParamOption(option=param[0],
+                                                        force_permissive=param[1]))
+            else:
+                self.params.append(_CallbackParamOption(value=param))
+
+
+consistency_table = Table('consistencyopt', Base.metadata,
+                          Column('left_id', Integer, ForeignKey('consistency.id')),
+                          Column('right_id', Integer, ForeignKey('baseoption.id'))
+                          )
+
+
+class _Consistency(Base):
+    __tablename__ = 'consistency'
+    id = Column(Integer, primary_key=True)
+    func = Column(PickleType)
+
+    def __init__(self, func, all_cons_opts):
+        self.func = func
+        for option in all_cons_opts:
+            option._consistencies.append(self)
+
+
+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')
+    __tablename__ = 'baseoption'
+    id = Column(Integer, primary_key=True)
+    _name = Column(String)
+    _type = Column(PickleType)
+    _informations = relationship('_Information')
+    _default = Column(PickleType)
+    _default_multi = Column(PickleType)
+    _requires = relationship('_RequireOption')
+    _multi = Column(Boolean)
+    _multitype = Column(String)
+    _callback = Column(PickleType)
+    _callback_params = relationship('_CallbackParam')
+    _validator = Column(PickleType)
+    _validator_params = relationship('_CallbackParam')
+    _parent = Column(Integer, ForeignKey('baseoption.id'))
+    _children = relationship('BaseOption', enable_typechecks=False)
+    _properties = relationship('_PropertyOption', secondary=property_table,
+                               backref=backref('options', enable_typechecks=False))
+    _warnings_only = Column(Boolean)
+    _readonly = Column(Boolean, default=False)
+    _consistencies = relationship('_Consistency', secondary=consistency_table,
+                                  backref=backref('options', enable_typechecks=False))
+    _choice_values = Column(PickleType)
+    _choice_open_values = Column(Boolean)
+    #FIXME devrait etre une table
+    _optiondescription_group_type = Column(String)
+    #__slots__ = ('_name', '_requires', '_properties', '_readonly',
+    #             '_calc_properties', '_impl_informations',
+    #             '_state_readonly', '_state_requires', '_stated')
 
-    def __init__(self, name, doc, requires, properties):
+    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._type = self.__class__
         self.impl_set_information('doc', doc)
-        self._calc_properties, self._requires = validate_requires_arg(
-            requires, self._name)
+        requires = validate_requires_arg(requires, self._name)
+        if requires is not None:
+            for values in requires.values():
+                for require in values.values():
+                    self._requires.append(_RequireOption(*require))
+        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,47 +263,45 @@ 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:
+                for key, values in validator_params.items():
+                    self._validator_params.append(_CallbackParam(key, values))
+        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:
+                for key, values in callback_params.items():
+                    self._callback_params.append(_CallbackParam(key, values))
+        if requires is not None and properties is not tuple():
+            set_forbidden_properties = set(properties) & set(requires.keys())
             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 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)
+        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
+        for prop in properties:
+            prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == prop).first()
+            if prop_obj is None:
+                prop_obj = _PropertyOption(prop)
+            self._properties.append(prop_obj)
+        self._warnings_only = warnings_only
 
+    # ____________________________________________________________
     # information
     def impl_set_information(self, key, value):
         """updates the information's attribute
@@ -126,21 +310,29 @@ 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
+        #FIXME pas append ! remplacer !
+        info = session.query(_Information).filter_by(option=self.id, key=key).first()
+        if info is None:
+            self._informations.append(_Information(key, value))
+        else:
+            info.value = 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]
+        info = session.query(_Information).filter_by(option=self.id, key=key).first()
+        if info is not None:
+            return info.value
         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,6 +449,9 @@ class BaseOption(object):
         for key, value in state.items():
             setattr(self, key, value)
 
+    def impl_getname(self):
+        return self._name
+
 
 class Option(BaseOption):
     """
@@ -264,16 +459,17 @@ class Option(BaseOption):
 
     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 +491,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)
+        session.add(self)
+        session.commit()
+
+    #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
+    #    if not name == '_option':
+    #        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)
+
+    def impl_getproperties(self):
+        for prop in self._properties:
+            yield(prop.name)
+
+    def impl_getrequires(self):
+        return self._requires
 
     def _launch_consistency(self, func, option, value, context, index,
                             all_cons_opts):
@@ -400,19 +606,36 @@ 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 '' in validator_params:
-                        lst = list(validator_params[''])
-                        lst.insert(0, val)
-                        validator_params[''] = tuple(lst)
-                    else:
-                        validator_params[''] = (val,)
+                class FakeCallbackParamOption(object):
+                    def __init__(self, option=None,
+                                 force_permissive=None,
+                                 value=None):
+                        self.option = option
+                        self.force_permissive = force_permissive
+                        self.value = value
+
+                class FakeCallbackParam(object):
+                    def __init__(self, key, params):
+                        self.name = key
+                        self.params = params
+                if self._validator_params != []:
+                    validator_params = []
+                    validator_params_option = [FakeCallbackParamOption(value=val)]
+                    #if '' in self._validator_params:
+                    #    for param in self._validator_params['']:
+                    #        if isinstance(param, tuple):
+                    #            validator_params_option.append(FakeCallbackParamOption(option=param[0], force_permissive=param[1]))
+                    #        else:
+                    #            validator_params_option.append(FakeCallbackParamOption(value=param))
+                    validator_params.append(FakeCallbackParam('', validator_params_option))
+                    for key in self._validator_params:
+                        if key.name != '':
+                            validator_params.append(key)
                 else:
-                    validator_params = {'': (val,)}
+                    validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=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 +646,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 +656,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 +672,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 +709,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 +727,6 @@ 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 = []
         for opt in other_opts:
             if not isinstance(opt, Option):
                 raise ConfigError(_('consistency should be set with an option'))
@@ -522,7 +746,7 @@ class Option(BaseOption):
             else:
                 self._launch_consistency(func, self, value, None,
                                          None, all_cons_opts)
-        self._consistencies.append((func, all_cons_opts))
+        _Consistency(func, all_cons_opts)
         self.impl_validate(self.impl_getdefault())
 
     def _cons_not_equal(self, opts, vals):
@@ -530,9 +754,10 @@ 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):
+        #FIXME
         if not load and self._callback is None:
             self._state_callback = None
         elif load and self._state_callback is None:
@@ -627,7 +852,7 @@ class ChoiceOption(Option):
     The option can also have the value ``None``
     """
 
-    __slots__ = ('_values', '_open_values')
+    #__slots__ = ('_values', '_open_values')
     _opt_type = 'string'
 
     def __init__(self, name, doc, values, default=None, default_multi=None,
@@ -639,11 +864,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,19 +876,21 @@ 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._open_values and not value in self._values:
+        if not self._choice_open_values and not value in self._choice_values:
             raise ValueError(_('value {0} is not permitted, '
                                'only {1} is allowed'
-                               '').format(value, self._values))
+                               '').format(value, self._choice_values))
 
 
 class BoolOption(Option):
@@ -726,10 +951,10 @@ else:
 
 
 class SymLinkOption(BaseOption):
-    __slots__ = ('_name', '_opt', '_state_opt')
+    #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
     _opt_type = 'symlink'
     #not return _opt consistencies
-    _consistencies = None
+    #_consistencies = None
 
     def __init__(self, name, opt):
         self._name = name
@@ -739,9 +964,13 @@ class SymLinkOption(BaseOption):
                                'for symlink {0}').format(name))
         self._opt = opt
         self._readonly = True
+        self._parent = None
+        self._type = self.__class__
+        session.add(self)
+        session.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,6 +984,13 @@ class SymLinkOption(BaseOption):
         del(self._state_opt)
         super(SymLinkOption, self)._impl_setstate(descr)
 
+    def impl_getname(self):
+        return self._name
+
+    def impl_get_information(self, key, default=None):
+        #FIXME ne devrait pas etre util si ?
+        return self._opt.impl_get_information(key, default)
+
 
 class IPOption(Option):
     "represents the choice of an ip"
@@ -938,7 +1174,7 @@ 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))
 
 
@@ -961,8 +1197,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):
@@ -972,7 +1208,7 @@ class DomainnameOption(Option):
     domainname:
     fqdn: with tld, not supported yet
     """
-    __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
+    __slots__ = ('_dom_type', '_allow_ip', '_allow_without_dot', '_domain_re')
     _opt_type = 'domainname'
 
     def __init__(self, name, doc, default=None, default_multi=None,
@@ -982,7 +1218,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]:
@@ -992,11 +1228,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 = '\.'
@@ -1023,7 +1259,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:
@@ -1103,11 +1339,11 @@ class OptionDescription(BaseOption):
     """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')
+    #_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'
 
     def __init__(self, name, doc, children, requires=None, properties=None):
@@ -1115,8 +1351,10 @@ class OptionDescription(BaseOption):
         :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)
+        session.add(self)
+        session.commit()
+        child_names = [child.impl_getname() for child in children]
         #better performance like this
         valid_child = copy(child_names)
         valid_child.sort()
@@ -1126,28 +1364,52 @@ class OptionDescription(BaseOption):
                 raise ConflictError(_('duplicate option name: '
                                       '{0}').format(child))
             old = child
-        self._children = (tuple(child_names), tuple(children))
+        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))
         self._cache_paths = None
         self._cache_consistencies = None
         # the group_type is useful for filtering OptionDescriptions in a config
-        self._group_type = groups.default
+        self._optiondescription_group_type = groups.default
+
+    def impl_getproperties(self):
+        #FIXME
+        for prop in self._properties:
+            yield(prop.name)
+
+    def impl_getrequires(self):
+        #FIXME
+        return self._requires
 
     def impl_getdoc(self):
         return self.impl_get_information('doc')
 
+    def impl_validate(self, *args):
+        #FIXME a voir ...
+        pass
+
     def __getattr__(self, name):
-        if name in self.__slots__:
-            return object.__getattribute__(self, name)
         try:
-            return self._children[1][self._children[0].index(name)]
+            if name.startswith('_') or name.startswith('impl_'):
+                return object.__getattribute__(self, name)
+            else:
+                #FIXME regression ...
+                for child in self._children:
+                    if child.impl_getname() == name:
+                        #convert to object
+                        return session.query(child._type).filter_by(id=child.id).first()
+                #return pouet#self._children[1][self._children[0].index(name)]
         except ValueError:
-            raise AttributeError(_('unknown Option {0} '
-                                   'in OptionDescription {1}'
-                                   '').format(name, self._name))
+            pass
+        raise AttributeError(_('unknown Option {0} '
+                               'in OptionDescription {1}'
+                               '').format(name, self.impl_getname()))
 
-    def impl_getkey(self, config):
-        return tuple([child.impl_getkey(getattr(config, child._name))
-                      for child in self.impl_getchildren()])
+    #def impl_getkey(self, config):
+    #    return tuple([child.impl_getkey(getattr(config, child.impl_getname()))
+    #                  for child in self.impl_getchildren()])
 
     def impl_getpaths(self, include_groups=False, _currpath=None):
         """returns a list of all paths in self, recursively
@@ -1157,7 +1419,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]))
@@ -1168,10 +1430,12 @@ class OptionDescription(BaseOption):
         return paths
 
     def impl_getchildren(self):
-        return self._children[1]
+        for child in self._children:
+            yield(session.query(child._type).filter_by(id=child.id).first())
 
     def impl_build_cache(self,
                          cache_path=None,
+                         cache_type=None,
                          cache_option=None,
                          _currpath=None,
                          _consistencies=None,
@@ -1188,53 +1452,69 @@ class OptionDescription(BaseOption):
             save = False
         if cache_path is None:
             cache_path = []
+            cache_type = []
             cache_option = []
         for option in self.impl_getchildren():
-            attr = option._name
-            if option in cache_option:
+            attr = option.impl_getname()
+            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)
+            cache_option.append(option.id)
             if not force_no_consistencies:
                 option._readonly = True
             cache_path.append(str('.'.join(_currpath + [attr])))
+            cache_type.append(option._type)
             if not isinstance(option, OptionDescription):
                 if not force_no_consistencies and \
-                        option._consistencies is not None:
+                        option._consistencies is not []:
                     for consistency in option._consistencies:
-                        func, all_cons_opts = consistency
+                        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,
+                option.impl_build_cache(cache_path, cache_type,
                                         cache_option,
                                         _currpath,
                                         _consistencies,
                                         force_no_consistencies)
                 _currpath.pop()
         if save:
-            self._cache_paths = (tuple(cache_option), tuple(cache_path))
+            self._cache_paths = (tuple(cache_option), tuple(cache_path), tuple(cache_type))
             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))
+                        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)
                 self._readonly = True
 
     def impl_get_opt_by_path(self, path):
         try:
-            return self._cache_paths[0][self._cache_paths[1].index(path)]
+            idx = self._cache_paths[1].index(path)
+            opt_id = self._cache_paths[0][idx]
+            opt_type = self._cache_paths[2][idx]
+            return session.query(opt_type).filter_by(id=opt_id).first()
         except ValueError:
             raise AttributeError(_('no option for path {0}').format(path))
 
+    def impl_get_opt_by_id(self, opt_id):
+        try:
+            idx = self._cache_paths[0].index(opt_id)
+            opt_type = self._cache_paths[2][idx]
+            return session.query(opt_type).filter_by(id=opt_id).first()
+        except ValueError:
+            raise AttributeError(_('no id {0} found').format(opt_id))
+
     def impl_get_path_by_opt(self, opt):
         try:
-            return self._cache_paths[1][self._cache_paths[0].index(opt)]
+            return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
         except ValueError:
             raise AttributeError(_('no option {0} found').format(opt))
 
@@ -1245,12 +1525,12 @@ class OptionDescription(BaseOption):
         :param group_type: an instance of `GroupType` or `MasterGroupType`
                               that lives in `setting.groups`
         """
-        if self._group_type != groups.default:
+        if self._optiondescription_group_type != groups.default:
             raise TypeError(_('cannot change group_type if already set '
-                            '(old {0}, new {1})').format(self._group_type,
+                            '(old {0}, new {1})').format(self._optiondescription_group_type,
                                                          group_type))
         if isinstance(group_type, groups.GroupType):
-            self._group_type = group_type
+            self._optiondescription_group_type = group_type
             if isinstance(group_type, groups.MasterGroupType):
                 #if master (same name has group) is set
                 #for collect all slaves
@@ -1259,16 +1539,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:
@@ -1276,7 +1556,7 @@ class OptionDescription(BaseOption):
                 if master is None:
                     raise ValueError(_('master group with wrong'
                                        ' master name for {0}'
-                                       ).format(self._name))
+                                       ).format(self.impl_getname()))
                 if master._callback is not None and master._callback[1] is not None:
                     for key, callbacks in master._callback[1].items():
                         for callbk in callbacks:
@@ -1294,7 +1574,7 @@ class OptionDescription(BaseOption):
                                ' not allowed').format(group_type))
 
     def impl_get_group_type(self):
-        return self._group_type
+        return getattr(groups, self._optiondescription_group_type)
 
     def _valid_consistency(self, option, value, context, index):
         if self._cache_consistencies is None:
@@ -1312,6 +1592,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`
@@ -1320,7 +1603,7 @@ class OptionDescription(BaseOption):
             self.impl_build_cache()
             descr = self
         super(OptionDescription, self)._impl_getstate(descr)
-        self._state_group_type = str(self._group_type)
+        self._state_group_type = str(self._optiondescription_group_type)
         for option in self.impl_getchildren():
             option._impl_getstate(descr)
 
@@ -1350,7 +1633,7 @@ class OptionDescription(BaseOption):
             self._cache_consistencies = None
             self.impl_build_cache(force_no_consistencies=True)
             descr = self
-        self._group_type = getattr(groups, self._state_group_type)
+        self._optiondescription_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():
@@ -1374,7 +1657,7 @@ def validate_requires_arg(requires, name):
                      the description of the requires dictionary
     """
     if requires is None:
-        return None, None
+        return None
     ret_requires = {}
     config_action = {}
 
@@ -1442,15 +1725,15 @@ 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():
-            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)
+    ## transform dict to tuple
+    #ret = []
+    #for opt_requires in ret_requires.values():
+    #    ret_action = []
+    #    for require in opt_requires.values():
+    #        ret_action.append((require[0], tuple(require[1]), require[2],
+    #                           require[3], require[4], require[5]))
+    #    ret.append(tuple(ret_action))
+    return ret_requires
 
 
 def validate_callback(callback, callback_params, type_):
@@ -1482,3 +1765,8 @@ def validate_callback(callback, callback_params, type_):
                                            ' not a {0} for second argument'
                                            ).format(type_, type(
                                                force_permissive)))
+
+#FIXME
+Base.metadata.create_all(engine)
+Session = sessionmaker(bind=engine)
+session = Session()