Merge branch 'master' into orm
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 13 Apr 2014 08:30:42 +0000 (10:30 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 13 Apr 2014 08:30:42 +0000 (10:30 +0200)
Conflicts:
test/test_config_api.py
tiramisu/autolib.py
tiramisu/config.py
tiramisu/option.py
tiramisu/value.py

17 files changed:
1  2 
test/test_config.py
test/test_config_api.py
test/test_option_calculation.py
test/test_option_consistency.py
test/test_option_setting.py
test/test_option_validator.py
tiramisu/autolib.py
tiramisu/config.py
tiramisu/option/baseoption.py
tiramisu/option/masterslave.py
tiramisu/option/option.py
tiramisu/option/optiondescription.py
tiramisu/setting.py
tiramisu/storage/__init__.py
tiramisu/storage/dictionary/__init__.py
tiramisu/storage/util.py
tiramisu/value.py

@@@ -83,13 -83,22 +83,22 @@@ def test_base_config_and_groups()
      assert config.gc.name == 'ref'
      assert config.bool is False
      nm = config.unwrap_from_path('gc.name')
 -    assert nm._name == 'name'
 +    assert nm.impl_getname() == 'name'
      gc = config.unwrap_from_path('gc')
 -    assert gc._name == 'gc'
 +    assert gc.impl_getname() == 'gc'
      #nm = config.unwrap_from_name('name')
 -    #assert nm._name == 'name'
 +    #assert nm.impl_getname() == 'name'
  
  
+ def test_base_config_force_permissive():
+     descr = make_description()
+     config = Config(descr)
+     config.read_write()
+     config.cfgimpl_get_settings().setpermissive(('hidden',))
+     raises(PropertiesOptionError, "config.getattr('boolop')")
+     assert config.getattr('boolop', force_permissive=True) is True
  def test_base_config_in_a_tree():
      "how options are organized into a tree, see :ref:`tree`"
      descr = make_description()
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -26,10 -25,10 +25,10 @@@ from tiramisu.setting import undefine
  
  
  def carry_out_calculation(option, config, callback, callback_params,
-                           index=None, max_len=None):
+                           index=undefined):
      """a function that carries out a calculation for an option's value
  
 -    :param name: the option
 +    :param option: the option
      :param config: the context config in order to have
                     the whole options available
      :param callback: the name of the callback function
      for key, callbacks in callback_params.items():
          for callbk in callbacks:
              if isinstance(callbk, tuple):
-                 # callbk is something link (opt, True|False)
-                 opt, force_permissive = callbk
-                 path = config.cfgimpl_get_description().impl_get_path_by_opt(
-                     opt)
-                 # get value
-                 try:
-                     value = config._getattr(path, force_permissive=True, validate=False)
-                     # convert to list, not modifie this multi
-                     if value.__class__.__name__ == 'Multi':
-                         value = list(value)
-                 except PropertiesOptionError as err:
-                     if force_permissive:
-                         continue
-                     raise ConfigError(_('unable to carry out a calculation, '
-                                         'option {0} has properties: {1} for: '
-                                         '{2}').format(option.impl_getname(),
-                                                       err.proptype,
-                                                       option._name))
-                 is_multi = False
-                 if opt.impl_is_multi():
-                     #opt is master, search if option is a slave
-                     if opt.impl_get_multitype() == multitypes.master:
-                         if option in opt.impl_get_master_slaves():
-                             is_multi = True
-                     #opt is slave, search if option is an other slaves
-                     elif opt.impl_get_multitype() == multitypes.slave:
-                         if option in opt.impl_get_master_slaves().impl_get_master_slaves():
-                             is_multi = True
-                 if is_multi:
-                     len_multi = len(value)
-                     one_is_multi = True
-                 tcparams.setdefault(key, []).append((value, is_multi))
+                 if len(callbk) == 1:
+                     tcparams.setdefault(key, []).append((config, False))
+                 else:
+                     # callbk is something link (opt, True|False)
+                     opt, force_permissive = callbk
+                     path = config.cfgimpl_get_description(
+                     ).impl_get_path_by_opt(opt)
+                     # get value
+                     try:
+                         value = config.getattr(path, force_permissive=True,
+                                                validate=False)
+                         # convert to list, not modifie this multi
+                         if value.__class__.__name__ == 'Multi':
+                             value = list(value)
+                     except PropertiesOptionError as err:
+                         if force_permissive:
+                             continue
+                         raise ConfigError(_('unable to carry out a calculation'
+                                             ', option {0} has properties: {1} '
 -                                            'for: {2}').format(opt._name,
++                                            'for: {2}').format(opt.impl_getname(),
+                                                                err.proptype,
 -                                                               option._name))
++                                                               option.impl_getname()))
+                     if opt.impl_is_master_slaves() and \
+                             opt.impl_get_master_slaves().in_same_group(option):
+                         len_multi = len(value)
+                         one_is_multi = True
+                         is_multi = True
+                     else:
+                         is_multi = False
+                     tcparams.setdefault(key, []).append((value, is_multi))
              else:
                  # callbk is a value and not a multi
                  tcparams.setdefault(key, []).append((callbk, False))
@@@ -68,29 -66,25 +66,28 @@@ class SubConfig(object)
          """:returns: tuple (config, name)"""
          path = path.split('.')
          for step in path[:-1]:
-             self = self._getattr(step,
-                                  force_permissive=force_permissive,
-                                  force_properties=force_properties)
+             self = self.getattr(step,
+                                 force_permissive=force_permissive)
          return self, path[-1]
  
 -    def __hash__(self):
 -        return hash(self.cfgimpl_get_description().impl_getkey(self))
 -
 -    def __eq__(self, other):
 -        "Config's comparison"
 -        if not isinstance(other, Config):
 -            return False
 -        return self.cfgimpl_get_description().impl_getkey(self) == \
 -            other.cfgimpl_get_description().impl_getkey(other)
 -
 -    def __ne__(self, other):
 -        "Config's comparison"
 -        if not isinstance(other, Config):
 -            return True
 -        return not self == other
 +    #def __hash__(self):
 +    #FIXME
 +    #    return hash(self.cfgimpl_get_description().impl_getkey(self))
 +
 +    #def __eq__(self, other):
 +    #FIXME
 +    #    "Config's comparison"
 +    #    if not isinstance(other, Config):
 +    #        return False
 +    #    return self.cfgimpl_get_description().impl_getkey(self) == \
 +    #        other.cfgimpl_get_description().impl_getkey(other)
 +
 +    #def __ne__(self, other):
 +    #FIXME
 +    #    "Config's comparison"
 +    #    if not isinstance(other, Config):
 +    #        return True
 +    #    return not self == other
  
      # ______________________________________________________________________
      def __iter__(self):
          iteration on Options and OptionDescriptions."""
          for child in self.cfgimpl_get_description().impl_getchildren():
              try:
-                 yield child.impl_getname(), getattr(self, child.impl_getname())
 -                yield child._name, self.getattr(child._name,
 -                                                force_permissive=force_permissive)
++                yield child.impl_getname(), self.getattr(child.impl_getname(),
++                                                    force_permissive=force_permissive)
              except GeneratorExit:
                  raise StopIteration
              except PropertiesOptionError:
                      if group_type is None or (group_type is not None and
                                                child.impl_get_group_type()
                                                == group_type):
-                         yield child.impl_getname(), getattr(self, child.impl_getname())
 -                        yield child._name, self.getattr(child._name,
 -                                                        force_permissive=force_permissive)
++                        yield child.impl_getname(), self.getattr(child.impl_getname(),
++                                                            force_permissive=force_permissive)
                  except GeneratorExit:
                      raise StopIteration
                  except PropertiesOptionError:
          :param first: return only one option if True, a list otherwise
          :return: find list or an exception if nothing has been found
          """
 -        def _filter_by_name():
 -            if byname is None or path == byname or \
 -                    path.endswith('.' + byname):
 -                return True
 -            return False
 -
          def _filter_by_value():
-             if byvalue is None:
+             if byvalue is undefined:
                  return True
              try:
-                 value = getattr(self, path)
+                 value = self.getattr(path, force_permissive=force_permissive)
                  if isinstance(value, Multi):
                      return byvalue in value
                  else:
              raise ValueError(_('unknown type_ type {0}'
                                 'for _find').format(type_))
          find_results = []
 -        opts, paths = self.cfgimpl_get_description()._cache_paths
 -        for index in range(0, len(paths)):
 -            option = opts[index]
 -            if isinstance(option, OptionDescription):
 -                continue
 -            path = paths[index]
 -            if _subpath is not None and not path.startswith(_subpath + '.'):
 -                continue
 -            if not _filter_by_name():
 -                continue
 +        # if value and/or check_properties are set, need all avalaible option
 +        # If first one has no good value or not good property check second one
 +        # and so on
 +        only_first = first is True and byvalue is None and check_properties is None
 +        options = self.cfgimpl_get_description().impl_get_options_paths(
 +            bytype, byname, _subpath, only_first)
 +        for path, option in options:
              if not _filter_by_value():
                  continue
 -            if not _filter_by_type():
 -                continue
              #remove option with propertyerror, ...
-             if byvalue is None and check_properties:
+             if byvalue is undefined and check_properties:
                  try:
-                     value = getattr(self, path)
+                     value = self.getattr(path,
+                                          force_permissive=force_permissive)
                  except PropertiesOptionError:
                      # a property restricts the access of the value
                      continue
          #withoption can be set to None below !
          if withoption is None:
              for opt in self.cfgimpl_get_description().impl_getchildren():
 -                path = opt._name
 +                path = opt.impl_getname()
-                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
+                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
+                                     force_permissive=force_permissive)
          if _currpath == []:
              options = dict(pathsvalues)
              return options
          return pathsvalues
  
-     def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten):
-         if isinstance(opt, OptionDescription):
-             pathsvalues += getattr(self, path).make_dict(flatten,
-                                                          _currpath +
-                                                          path.split('.'))
-         else:
-             try:
-                 value = self._getattr(opt.impl_getname())
+     def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
+                        force_permissive=False):
+         try:
+             if isinstance(opt, OptionDescription):
+                 pathsvalues += self.getattr(path,
+                                             force_permissive=force_permissive).make_dict(
+                                                 flatten,
+                                                 _currpath + path.split('.'),
+                                                 force_permissive=force_permissive)
+             else:
 -                value = self.getattr(opt._name,
++                value = self.getattr(opt.impl_getname(),
+                                      force_permissive=force_permissive)
                  if flatten:
 -                    name = opt._name
 +                    name = opt.impl_getname()
                  else:
 -                    name = '.'.join(_currpath + [opt._name])
 +                    name = '.'.join(_currpath + [opt.impl_getname()])
                  pathsvalues.append((name, value))
-             except PropertiesOptionError:
-                 pass  # this just a hidden or disabled option
+         except PropertiesOptionError:
+             pass
  
      def cfgimpl_get_path(self):
          descr = self.cfgimpl_get_description()
index 0000000,68cd189..25c5e11
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,808 +1,867 @@@
 -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')
+ # -*- coding: utf-8 -*-
+ # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
+ #
+ # This program is free software: you can redistribute it and/or modify it
+ # under the terms of the GNU Lesser General Public License as published by the
+ # Free Software Foundation, either version 3 of the License, or (at your
+ # option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful, but WITHOUT
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+ # details.
+ #
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ #
+ # The original `Config` design model is unproudly borrowed from
+ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+ # the whole pypy projet is under MIT licence
+ # ____________________________________________________________
+ import re
+ from copy import copy, deepcopy
+ from types import FunctionType
+ import warnings
+ from tiramisu.i18n import _
+ from tiramisu.setting import log, undefined
+ from tiramisu.autolib import carry_out_calculation
+ from tiramisu.error import ConfigError, ValueWarning
++from tiramisu.storage import get_storages_option
++
++
++StorageBase = get_storages_option('base')
+ name_regexp = re.compile(r'^\d+')
+ forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
+                    'make_dict', 'unwrap_from_path', 'read_only',
+                    'read_write', 'getowner', 'set_contexts')
+ def valid_name(name):
+     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
+     if not isinstance(name, str):
+         return False
+     if re.match(name_regexp, name) is None and not name.startswith('_') \
+             and name not in forbidden_names \
+             and not name.startswith('impl_') \
+             and not name.startswith('cfgimpl_'):
+         return True
+     else:
+         return False
+ def validate_callback(callback, callback_params, type_):
+     if type(callback) != FunctionType:
+         raise ValueError(_('{0} must be a function').format(type_))
+     if callback_params is not None:
+         if not isinstance(callback_params, dict):
+             raise ValueError(_('{0}_params must be a dict').format(type_))
+         for key, callbacks in callback_params.items():
+             if key != '' and len(callbacks) != 1:
+                 raise ValueError(_("{0}_params with key {1} mustn't have "
+                                    "length different to 1").format(type_,
+                                                                    key))
+             if not isinstance(callbacks, tuple):
+                 raise ValueError(_('{0}_params must be tuple for key "{1}"'
+                                    ).format(type_, key))
+             for callbk in callbacks:
+                 if isinstance(callbk, tuple):
+                     if len(callbk) == 1:
+                         if callbk != (None,):
+                             raise ValueError(_('{0}_params with length of '
+                                                'tuple as 1 must only have '
+                                                'None as first value'))
+                     elif len(callbk) != 2:
+                         raise ValueError(_('{0}_params must only have 1 or 2 '
+                                            'as length'))
+                     else:
+                         option, force_permissive = callbk
+                         if type_ == 'validator' and not force_permissive:
+                             raise ValueError(_('validator not support tuple'))
+                         if not isinstance(option, Option) and not \
+                                 isinstance(option, SymLinkOption):
+                             raise ValueError(_('{0}_params must have an option'
+                                                ' not a {0} for first argument'
+                                                ).format(type_, type(option)))
+                         if force_permissive not in [True, False]:
+                             raise ValueError(_('{0}_params must have a boolean'
+                                                ' not a {0} for second argument'
+                                                ).format(type_, type(
+                                                    force_permissive)))
+ #____________________________________________________________
+ #
 -    def __init__(self, name, doc, requires, properties):
++class Base(StorageBase):
++    __slots__ = tuple()
 -        self._impl_informations = {}
++    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):
+         if not valid_name(name):
+             raise ValueError(_("invalid name: {0} for option").format(name))
+         self._name = name
 -        self._calc_properties, self._requires = validate_requires_arg(
 -            requires, self._name)
++        self._readonly = False
++        self._informations = {}
+         self.impl_set_information('doc', doc)
 -        if self._calc_properties is not None and properties is not tuple():
 -            set_forbidden_properties = set(properties) & self._calc_properties
++        if requires is not None:
++            self._calc_properties, self._requires = validate_requires_arg(
++                requires, self._name)
++        else:
++            self._calc_properties = frozenset()
++            self._requires = []
++        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._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):
+             raise TypeError(_('invalid properties type {0} for {1},'
+                             ' must be a tuple').format(
+                                 type(properties),
+                                 self._name))
 -        self._properties = properties  # 'hidden', 'disabled'...
++        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)))
 -    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 multi and default is None:
++            self._default = []
++        else:
++            self._default = default
++        self._properties = properties
++        self._warnings_only = warnings_only
++        ret = super(Base, self).__init__()
++        self.impl_validate(self._default)
++        return ret
 -        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)
 -        self._impl_informations[key] = 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__ = tuple()
+     # information
+     def impl_set_information(self, key, value):
+         """updates the information's attribute
+         (which is a dictionary)
+         :param key: information's key (ex: "help", "doc"
+         :param value: information's value (ex: "the help string")
+         """
 -        if key in self._impl_informations:
 -            return self._impl_informations[key]
++        self._informations[key] = value
+     def impl_get_information(self, key, default=undefined):
+         """retrieves one information's item
+         :param key: the item string (ex: "help")
+         """
 -class Option(BaseOption):
++        if key in self._informations:
++            return self._informations[key]
+         elif default is not undefined:
+             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
+         :type descr: :class:`tiramisu.option.OptionDescription`
+         :param load: `True` if we are at the init of the option description
+         :type load: bool
+         """
+         if not load and self._requires is None:
+             self._state_requires = None
+         elif load and self._state_requires is None:
+             self._requires = None
+             del(self._state_requires)
+         else:
+             if load:
+                 _requires = self._state_requires
+             else:
+                 _requires = self._requires
+             new_value = []
+             for requires in _requires:
+                 new_requires = []
+                 for require in requires:
+                     if load:
+                         new_require = [descr.impl_get_opt_by_path(require[0])]
+                     else:
+                         new_require = [descr.impl_get_path_by_opt(require[0])]
+                     new_require.extend(require[1:])
+                     new_requires.append(tuple(new_require))
+                 new_value.append(tuple(new_requires))
+             if load:
+                 del(self._state_requires)
+                 self._requires = new_value
+             else:
+                 self._state_requires = new_value
+     # serialize
+     def _impl_getstate(self, descr):
+         """the under the hood stuff that need to be done
+         before the serialization.
+         :param descr: the parent :class:`tiramisu.option.OptionDescription`
+         """
+         self._stated = True
+         for func in dir(self):
+             if func.startswith('_impl_convert_'):
+                 getattr(self, func)(descr)
+         self._state_readonly = self._readonly
+     def __getstate__(self, stated=True):
+         """special method to enable the serialization with pickle
+         Usualy, a `__getstate__` method does'nt need any parameter,
+         but somme under the hood stuff need to be done before this action
+         :parameter stated: if stated is `True`, the serialization protocol
+                            can be performed, not ready yet otherwise
+         :parameter type: bool
+         """
+         try:
+             self._stated
+         except AttributeError:
+             raise SystemError(_('cannot serialize Option, '
+                                 'only in OptionDescription'))
+         slots = set()
+         for subclass in self.__class__.__mro__:
+             if subclass is not object:
+                 slots.update(subclass.__slots__)
+         slots -= frozenset(['_cache_paths', '_cache_consistencies',
+                             '__weakref__'])
+         states = {}
+         for slot in slots:
+             # remove variable if save variable converted
+             # in _state_xxxx variable
+             if '_state' + slot not in slots:
+                 if slot.startswith('_state'):
+                     # should exists
+                     states[slot] = getattr(self, slot)
+                     # remove _state_xxx variable
+                     self.__delattr__(slot)
+                 else:
+                     try:
+                         states[slot] = getattr(self, slot)
+                     except AttributeError:
+                         pass
+         if not stated:
+             del(states['_stated'])
+         return states
+     # unserialize
+     def _impl_setstate(self, descr):
+         """the under the hood stuff that need to be done
+         before the serialization.
+         :type descr: :class:`tiramisu.option.OptionDescription`
+         """
+         for func in dir(self):
+             if func.startswith('_impl_convert_'):
+                 getattr(self, func)(descr, load=True)
+         try:
+             self._readonly = self._state_readonly
+             del(self._state_readonly)
+             del(self._stated)
+         except AttributeError:
+             pass
+     def __setstate__(self, state):
+         """special method that enables us to serialize (pickle)
+         Usualy, a `__setstate__` method does'nt need any parameter,
+         but somme under the hood stuff need to be done before this action
+         :parameter state: a dict is passed to the loads, it is the attributes
+                           of the options object
+         :type state: dict
+         """
+         for key, value in state.items():
+             setattr(self, key, value)
++    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 name not in ('_option', '_is_build_cache') \
++                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
++                except (KeyError, AttributeError):
++                    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(BaseOption, self).__setattr__(name, value)
++
++    def impl_is_readonly(self):
++        try:
++            if self._readonly is True:
++                return True
++        except AttributeError:
++            pass
++        return False
++
++    def impl_getname(self):
++        return self._name
 -    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
 -                 '_state_callback', '_callback', '_consistencies',
 -                 '_warnings_only', '_master_slaves', '_state_consistencies',
 -                 '__weakref__')
++
++class OnlyOption(BaseOption):
++    __slots__ = tuple()
++
++
++class Option(OnlyOption):
+     """
+     Abstract base class for configuration option's.
+     Reminder: an Option object is **not** a container for the value.
+     """
 -        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._default_multi = default_multi
 -        self._warnings_only = warnings_only
 -        self.impl_validate(default)
 -        self._default = default
 -        self._consistencies = None
++#    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
++#                 '_state_callback', '_callback',
++#                 '_consistencies', '_warnings_only', '_master_slaves',
++#                 '_state_consistencies', '__weakref__')
++    __slots__ = tuple()
+     _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):
+         """
+         :param name: the option's name
+         :param doc: the option's description
+         :param default: specifies the default value of the option,
+                         for a multi : ['bla', 'bla', 'bla']
+         :param default_multi: 'bla' (used in case of a reset to default only at
+                         a given index)
+         :param requires: is a list of names of options located anywhere
+                          in the configuration.
+         :param multi: if true, the option's value is a list
+         :param callback: the name of a function. If set, the function's output
+                          is responsible of the option's value
+         :param callback_params: the callback's parameter
+         :param validator: the name of a function which stands for a custom
+                           validation of the value
+         :param validator_params: the validator's parameters
+         :param properties: tuple of default properties
+         :param warnings_only: _validator and _consistencies don't raise if True
+                              Values()._warning contain message
+         """
 -                if self._validator[1] is not None:
 -                    validator_params = deepcopy(self._validator[1])
++        super(Option, self).__init__(name, doc, default, default_multi,
++                                     requires, multi, callback,
++                                     callback_params, validator,
++                                     validator_params, properties,
++                                     warnings_only)
++
++    def impl_getrequires(self):
++        return self._requires
+     def _launch_consistency(self, func, option, value, context, index,
+                             all_cons_opts, warnings_only):
+         """Launch consistency now
+         :param func: function name, this name should start with _cons_
+         :type func: `str`
+         :param option: option that value is changing
+         :type option: `tiramisu.option.Option`
+         :param value: new value of this option
+         :param context: Config's context, if None, check default value instead
+         :type context: `tiramisu.config.Config`
+         :param index: only for multi option, consistency should be launch for
+                       specified index
+         :type index: `int`
+         :param all_cons_opts: all options concerne by this consistency
+         :type all_cons_opts: `list` of `tiramisu.option.Option`
+         :param warnings_only: specific raise error for warning
+         :type warnings_only: `boolean`
+         """
+         if context is not None:
+             descr = context.cfgimpl_get_description()
+         all_cons_vals = []
+         for opt in all_cons_opts:
+             #get value
+             if option == opt:
+                 opt_value = value
+             else:
+                 #if context, calculate value, otherwise get default value
+                 if context is not None:
+                     opt_value = context.getattr(
+                         descr.impl_get_path_by_opt(opt), validate=False,
+                         force_permissive=True)
+                 else:
+                     opt_value = opt.impl_getdefault()
+             #append value
+             if not self.impl_is_multi() or option == opt:
+                 all_cons_vals.append(opt_value)
+             else:
+                 #value is not already set, could be higher index
+                 try:
+                     all_cons_vals.append(opt_value[index])
+                 except IndexError:
+                     #so return if no value
+                     return
+         getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
+     def impl_validate(self, value, context=None, validate=True,
+                       force_index=None):
+         """
+         :param value: the option's value
+         :param context: Config's context
+         :type context: :class:`tiramisu.config.Config`
+         :param validate: if true enables ``self._validator`` validation
+         :type validate: boolean
+         :param force_index: if multi, value has to be a list
+                                not if force_index is not None
+         :type force_index: integer
+         """
+         if not validate:
+             return
+         def val_validator(val):
+             if self._validator is not None:
 -                                      callback=self._validator[0],
++                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)
+                         validator_params[''] = tuple(lst)
+                     else:
+                         validator_params[''] = (val,)
+                 else:
+                     validator_params = {'': (val,)}
+                 # Raise ValueError if not valid
+                 carry_out_calculation(self, config=context,
 -                                   '').format(self._name, err))
++                                      callback=self._validator,
+                                       callback_params=validator_params)
+         def do_validation(_value, _index=None):
+             if _value is None:
+                 return
+             # option validation
+             try:
+                 self._validate(_value)
+             except ValueError as err:
+                 log.debug('do_validation: value: {0} index: {1}'.format(
+                     _value, _index), exc_info=True)
+                 raise ValueError(_('invalid value for option {0}: {1}'
 -                    self._name), exc_info=True)
++                                   '').format(self.impl_getname(), err))
+             error = None
+             warning = None
+             try:
+                 # valid with self._validator
+                 val_validator(_value)
++                # if not context launch consistency validation
++                if context is not None:
++                    descr._valid_consistency(self, _value, context, _index)
+                 self._second_level_validation(_value, self._warnings_only)
+             except ValueError as error:
+                 log.debug(_('do_validation for {0}: error in value').format(
 -                    self._name), exc_info=True)
++                    self.impl_getname()), exc_info=True)
+                 if self._warnings_only:
+                     warning = error
+                     error = None
+             except ValueWarning as warning:
+                 log.debug(_('do_validation for {0}: warning in value').format(
 -                        self._name), exc_info=True)
++                    self.impl_getname()), exc_info=True)
+                 pass
+             if error is None and warning is None:
+                 try:
+                     # if context launch consistency validation
+                     if context is not None:
+                         descr._valid_consistency(self, _value, context, _index)
+                 except ValueError as error:
+                     log.debug(_('do_validation for {0}: error in consistency').format(
 -                        self._name), exc_info=True)
++                        self.impl_getname()), exc_info=True)
+                     pass
+                 except ValueWarning as warning:
+                     log.debug(_('do_validation for {0}: warning in consistency').format(
 -                    self._name, warning)
++                        self.impl_getname()), exc_info=True)
+                     pass
+             if warning:
+                 msg = _("warning on the value of the option {0}: {1}").format(
 -                raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name))
++                    self.impl_getname(), warning)
+                 warnings.warn_explicit(ValueWarning(msg, self),
+                                        ValueWarning,
+                                        self.__class__.__name__, 0)
+             elif error:
+                 raise ValueError(_("invalid value for option {0}: {1}").format(
+                     self._name, error))
+         # generic calculation
+         if context is not None:
+             descr = context.cfgimpl_get_description()
+         if not self._multi or force_index is not None:
+             do_validation(value, force_index)
+         else:
+             if not isinstance(value, list):
 -    def impl_getkey(self, value):
 -        return value
++                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)
+     def impl_getdefault(self):
+         "accessing the default value"
+         if isinstance(self._default, list):
+             return copy(self._default)
+         return self._default
+     def impl_getdefault_multi(self):
+         "accessing the default value for a multi"
+         return self._default_multi
+     def impl_is_master_slaves(self, type_='both'):
+         """FIXME
+         """
+         try:
+             self._master_slaves
+             if type_ in ('both', 'master') and \
+                     self._master_slaves.is_master(self):
+                 return True
+             if type_ in ('both', 'slave') and \
+                     not self._master_slaves.is_master(self):
+                 return True
+         except:
+             pass
+         return False
+     def impl_get_master_slaves(self):
+         return self._master_slaves
+     def impl_is_empty_by_default(self):
+         "no default value has been set yet"
+         if ((not self.impl_is_multi() and self._default is None) or
+                 (self.impl_is_multi() and (self._default == []
+                                            or None in self._default))):
+             return True
+         return False
+     def impl_getdoc(self):
+         "accesses the Option's doc"
+         return self.impl_get_information('doc')
+     def impl_has_callback(self):
+         "to know if a callback has been defined or not"
+         if self._callback is None:
+             return False
+         else:
+             return True
 -        if self._consistencies is None:
 -            self._consistencies = []
++    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
+     def impl_add_consistency(self, func, *other_opts, **params):
+         """Add consistency means that value will be validate with other_opts
+         option's values.
+         :param func: function's name
+         :type func: `str`
+         :param other_opts: options used to validate value
+         :type other_opts: `list` of `tiramisu.option.Option`
+         :param params: extra params (only warnings_only are allowed)
+         """
 -        self._consistencies.append((func, all_cons_opts, params))
++        if self.impl_is_readonly():
++            raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
++                                   " read-only").format(
++                                       self.__class__.__name__,
++                                       self._name))
+         warnings_only = params.get('warnings_only', False)
+         for opt in other_opts:
+             if not isinstance(opt, Option):
+                 raise ConfigError(_('consistency must be set with an option'))
+             if self is opt:
+                 raise ConfigError(_('cannot add consistency with itself'))
+             if self.impl_is_multi() != opt.impl_is_multi():
+                 raise ConfigError(_('every options in consistency must be '
+                                     'multi or none'))
+         func = '_cons_{0}'.format(func)
+         all_cons_opts = tuple([self] + list(other_opts))
+         value = self.impl_getdefault()
+         if value is not None:
+             if self.impl_is_multi():
+                 for idx, val in enumerate(value):
+                     self._launch_consistency(func, self, val, None,
+                                              idx, all_cons_opts, warnings_only)
+             else:
+                 self._launch_consistency(func, self, value, None,
+                                          None, all_cons_opts, warnings_only)
 -                    raise ValueError(msg.format(opts[idx_inf]._name,
 -                                                opts[idx_inf + idx_sup + 1]._name))
++        self._add_consistency(func, all_cons_opts, params)
+         self.impl_validate(self.impl_getdefault())
+     def _cons_not_equal(self, opts, vals, warnings_only):
+         for idx_inf, val_inf in enumerate(vals):
+             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
+                 if val_inf == val_sup is not None:
+                     if warnings_only:
+                         msg = _("same value for {0} and {1}, should be different")
+                     else:
+                         msg = _("same value for {0} and {1}, must be different")
 -class SymLinkOption(BaseOption):
 -    __slots__ = ('_name', '_opt', '_state_opt')
 -    _opt_type = 'symlink'
++                    raise ValueError(msg.format(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:
+             self._state_callback = None
+         elif load and self._state_callback is None:
+             self._callback = None
+             del(self._state_callback)
+         else:
+             if load:
+                 callback, callback_params = self._state_callback
+             else:
+                 callback, callback_params = self._callback
+             if callback_params is not None:
+                 cllbck_prms = {}
+                 for key, values in callback_params.items():
+                     vls = []
+                     for value in values:
+                         if isinstance(value, tuple):
+                             if load:
+                                 value = (descr.impl_get_opt_by_path(value[0]),
+                                          value[1])
+                             else:
+                                 value = (descr.impl_get_path_by_opt(value[0]),
+                                          value[1])
+                         vls.append(value)
+                     cllbck_prms[key] = tuple(vls)
+             else:
+                 cllbck_prms = None
+             if load:
+                 del(self._state_callback)
+                 self._callback = (callback, cllbck_prms)
+             else:
+                 self._state_callback = (callback, cllbck_prms)
+     # serialize/unserialize
+     def _impl_convert_consistencies(self, descr, load=False):
+         """during serialization process, many things have to be done.
+         one of them is the localisation of the options.
+         The paths are set once for all.
+         :type descr: :class:`tiramisu.option.OptionDescription`
+         :param load: `True` if we are at the init of the option description
+         :type load: bool
+         """
+         if not load and self._consistencies is None:
+             self._state_consistencies = None
+         elif load and self._state_consistencies is None:
+             self._consistencies = None
+             del(self._state_consistencies)
+         else:
+             if load:
+                 consistencies = self._state_consistencies
+             else:
+                 consistencies = self._consistencies
+             new_value = []
+             for consistency in consistencies:
+                 values = []
+                 for obj in consistency[1]:
+                     if load:
+                         values.append(descr.impl_get_opt_by_path(obj))
+                     else:
+                         values.append(descr.impl_get_path_by_opt(obj))
+                 new_value.append((consistency[0], tuple(values), consistency[2]))
+             if load:
+                 del(self._state_consistencies)
+                 self._consistencies = new_value
+             else:
+                 self._state_consistencies = new_value
+     def _second_level_validation(self, value, warnings_only):
+         pass
+ def validate_requires_arg(requires, name):
+     """check malformed requirements
+     and tranform dict to internal tuple
+     :param requires: have a look at the
+                      :meth:`tiramisu.setting.Settings.apply_requires` method to
+                      know more about
+                      the description of the requires dictionary
+     """
+     if requires is None:
+         return None, None
+     ret_requires = {}
+     config_action = {}
+     # start parsing all requires given by user (has dict)
+     # transforme it to a tuple
+     for require in requires:
+         if not type(require) == dict:
+             raise ValueError(_("malformed requirements type for option:"
+                                " {0}, must be a dict").format(name))
+         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
+                       'same_action')
+         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
+         if unknown_keys != frozenset():
+             raise ValueError('malformed requirements for option: {0}'
+                              ' unknown keys {1}, must only '
+                              '{2}'.format(name,
+                                           unknown_keys,
+                                           valid_keys))
+         # prepare all attributes
+         try:
+             option = require['option']
+             expected = require['expected']
+             action = require['action']
+         except KeyError:
+             raise ValueError(_("malformed requirements for option: {0}"
+                                " require must have option, expected and"
+                                " action keys").format(name))
+         if action == 'force_store_value':
+             raise ValueError(_("malformed requirements for option: {0}"
+                                " action cannot be force_store_value"
+                                ).format(name))
+         inverse = require.get('inverse', False)
+         if inverse not in [True, False]:
+             raise ValueError(_('malformed requirements for option: {0}'
+                                ' inverse must be boolean'))
+         transitive = require.get('transitive', True)
+         if transitive not in [True, False]:
+             raise ValueError(_('malformed requirements for option: {0}'
+                                ' transitive must be boolean'))
+         same_action = require.get('same_action', True)
+         if same_action not in [True, False]:
+             raise ValueError(_('malformed requirements for option: {0}'
+                                ' same_action must be boolean'))
+         if not isinstance(option, Option):
+             raise ValueError(_('malformed requirements '
+                                'must be an option in option {0}').format(name))
+         if option.impl_is_multi():
+             raise ValueError(_('malformed requirements option {0} '
+                                'must not be a multi').format(name))
+         if expected is not None:
+             try:
+                 option._validate(expected)
+             except ValueError as err:
+                 raise ValueError(_('malformed requirements second argument '
+                                    'must be valid for option {0}'
+                                    ': {1}').format(name, err))
+         if action in config_action:
+             if inverse != config_action[action]:
+                 raise ValueError(_("inconsistency in action types"
+                                    " for option: {0}"
+                                    " action: {1}").format(name, action))
+         else:
+             config_action[action] = inverse
+         if action not in ret_requires:
+             ret_requires[action] = {}
+         if option not in ret_requires[action]:
+             ret_requires[action][option] = (option, [expected], action,
+                                             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)
 -    _consistencies = None
++class SymLinkOption(OnlyOption):
++    #FIXME : et avec sqlalchemy ca marche vraiment ?
++    __slots__ = ('_opt',)
+     #not return _opt consistencies
 -        if name in ('_name', '_opt', '_opt_type', '_readonly'):
++    #_consistencies = None
+     def __init__(self, name, opt):
+         self._name = name
+         if not isinstance(opt, Option):
+             raise ValueError(_('malformed symlinkoption '
+                                'must be an option '
+                                'for symlink {0}').format(name))
+         self._opt = opt
+         self._readonly = True
++        return super(Base, self).__init__()
+     def __getattr__(self, name):
++        if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
+             return object.__getattr__(self, name)
+         else:
+             return getattr(self._opt, name)
+     def _impl_getstate(self, descr):
+         super(SymLinkOption, self)._impl_getstate(descr)
+         self._state_opt = descr.impl_get_path_by_opt(self._opt)
+     def _impl_setstate(self, descr):
+         self._opt = descr.impl_get_opt_by_path(self._state_opt)
+         del(self._state_opt)
+         super(SymLinkOption, self)._impl_setstate(descr)
++
++    def impl_get_information(self, key, default=undefined):
++        return self._opt.impl_get_information(key, default)
index 0000000,9e0426d..4298cc7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,187 +1,188 @@@
 -        if self.master._callback is not None and self.master._callback[1] is not None:
 -            for key, callbacks in self.master._callback[1].items():
+ # -*- coding: utf-8 -*-
+ "master slave support"
+ # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
+ #
+ # This program is free software: you can redistribute it and/or modify it
+ # under the terms of the GNU Lesser General Public License as published by the
+ # Free Software Foundation, either version 3 of the License, or (at your
+ # option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful, but WITHOUT
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+ # details.
+ #
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ #
+ # The original `Config` design model is unproudly borrowed from
+ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+ # the whole pypy projet is under MIT licence
+ # ____________________________________________________________
+ from tiramisu.i18n import _
+ from tiramisu.setting import log
+ from tiramisu.error import SlaveError, ConfigError
+ from .baseoption import SymLinkOption, Option
+ class MasterSlaves(object):
+     __slots__ = ('master', 'slaves')
+     def __init__(self, name, childs):
+         #if master (same name has group) is set
+         #for collect all slaves
+         self.master = None
+         slaves = []
+         for child in childs:
+             if isinstance(child, SymLinkOption):
+                 raise ValueError(_("master group {0} shall not have "
+                                    "a symlinkoption").format(name))
+             if not isinstance(child, Option):
+                 raise ValueError(_("master group {0} shall not have "
+                                    "a subgroup").format(name))
+             if not child.impl_is_multi():
+                 raise ValueError(_("not allowed option {0} "
+                                    "in group {1}"
+                                    ": this option is not a multi"
+                                    "").format(child._name, name))
+             if child._name == name:
+                 self.master = child
+             else:
+                 slaves.append(child)
+         if self.master is None:
+             raise ValueError(_('master group with wrong'
+                                ' master name for {0}'
+                                ).format(name))
++        callback, callback_params = self.master.impl_get_callback()
++        if callback is not None and callback_params is not None:
++            for key, callbacks in callback_params.items():
+                 for callbk in callbacks:
+                     if isinstance(callbk, tuple):
+                         if callbk[0] in slaves:
+                             raise ValueError(_("callback of master's option shall "
+                                                "not refered a slave's ones"))
+         #everything is ok, store references
+         self.slaves = tuple(slaves)
+         for child in childs:
+             child._master_slaves = self
+     def is_master(self, opt):
+         return opt == self.master
+     def in_same_group(self, opt):
+         return opt == self.master or opt in self.slaves
+     def reset(self, values):
+         for slave in self.slaves:
+             values.reset(slave)
+     def pop(self, values, index):
+         #FIXME pas test de meta ...
+         for slave in self.slaves:
+             if not values.is_default_owner(slave, validate_properties=False,
+                                            validate_meta=False):
+                 values._get_cached_item(slave, validate=False,
+                                         validate_properties=False
+                                         ).pop(index, force=True)
+         pass
+     def getitem(self, values, opt, path, validate, force_permissive,
+                 force_properties, validate_properties):
+         if opt == self.master:
+             value = values._get_validated_value(opt, path, validate,
+                                                 force_permissive,
+                                                 force_properties,
+                                                 validate_properties)
+             if validate is True:
+                 masterlen = len(value)
+                 for slave in self.slaves:
+                     try:
+                         slave_path = values._get_opt_path(slave)
+                         slave_value = values._get_validated_value(slave,
+                                                                   slave_path,
+                                                                   False,
+                                                                   False,
+                                                                   None, False,
+                                                                   None)  # not undefined
+                         slavelen = len(slave_value)
+                         self.validate_slave_length(masterlen, slavelen, slave._name)
+                     except ConfigError:
+                         pass
+             return value
+         else:
+             value = values._get_validated_value(opt, path, validate,
+                                                 force_permissive,
+                                                 force_properties,
+                                                 validate_properties,
+                                                 None)  # not undefined
+             return self.get_slave_value(values, opt, value, validate, validate_properties)
+     def setitem(self, values, opt, value, path):
+         if opt == self.master:
+             masterlen = len(value)
+             for slave in self.slaves:
+                 slave_path = values._get_opt_path(slave)
+                 slave_value = values._get_validated_value(slave,
+                                                           slave_path,
+                                                           False,
+                                                           False,
+                                                           None, False,
+                                                           None)  # not undefined
+                 slavelen = len(slave_value)
+                 self.validate_slave_length(masterlen, slavelen, slave._name)
+         else:
+             self.validate_slave_length(self.get_length(values), len(value),
+                                        opt._name, setitem=True)
+     def get_length(self, values, validate=True):
+         masterp = values._get_opt_path(self.master)
+         return len(self.getitem(values, self.master, masterp, validate, False,
+                                 None, True))
+     def validate_slave_length(self, masterlen, valuelen, name, setitem=False):
+         if valuelen > masterlen or (valuelen < masterlen and setitem):
+             log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
+                       'setitem: {2}'.format(masterlen, valuelen, setitem))
+             raise SlaveError(_("invalid len for the slave: {0}"
+                                " which has {1} as master").format(
+                                    name, self.master._name))
+     def get_slave_value(self, values, opt, value, validate=True,
+                         validate_properties=True):
+         """
+         if master has length 0:
+             return []
+         if master has length bigger than 0:
+             if default owner:
+                 if has callback:
+                     if return a list:
+                         list same length as master: return list
+                         list is smaller than master: return list + None
+                         list is greater than master: raise SlaveError
+                 if has default value:
+                     list same length as master: return list
+                     list is smaller than master: return list + None
+                     list is greater than master: raise SlaveError
+                 if has default_multi value:
+                     return default_multi * master's length
+             if has value:
+                 list same length as master: return list
+                 list is smaller than master: return list + None
+                 list is greater than master: raise SlaveError
+         """
+         #if slave, had values until master's one
+         masterlen = self.get_length(values, validate)
+         valuelen = len(value)
+         if validate:
+             self.validate_slave_length(masterlen, valuelen, opt._name)
+         path = values._get_opt_path(opt)
+         if valuelen < masterlen:
+             for num in range(0, masterlen - valuelen):
+                 index = valuelen + num
+                 value.append(values._get_validated_value(opt, path, True,
+                                                          False, None,
+                                                          validate_properties,
+                                                          index=index),
+                              setitem=False,
+                              force=True)
+         return value
index 0000000,8a0cda6..d707940
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,525 +1,508 @@@
 -
 -    __slots__ = ('_values', '_open_values')
 -    _opt_type = 'string'
+ # -*- coding: utf-8 -*-
+ "option types and option description"
+ # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
+ #
+ # This program is free software: you can redistribute it and/or modify it
+ # under the terms of the GNU Lesser General Public License as published by the
+ # Free Software Foundation, either version 3 of the License, or (at your
+ # option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful, but WITHOUT
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+ # details.
+ #
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ #
+ # The original `Config` design model is unproudly borrowed from
+ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+ # the whole pypy projet is under MIT licence
+ # ____________________________________________________________
+ import re
+ import sys
+ from IPy import IP
+ from tiramisu.error import ConfigError
+ from tiramisu.i18n import _
+ from .baseoption import Option
+ class ChoiceOption(Option):
+     """represents a choice out of several objects.
+     The option can also have the value ``None``
+     """
 -        self._values = values
++    __slots__ = tuple()
+     def __init__(self, name, doc, values, default=None, default_multi=None,
+                  requires=None, multi=False, callback=None,
+                  callback_params=None, open_values=False, validator=None,
+                  validator_params=None, properties=None, warnings_only=False):
+         """
+         :param values: is a list of values the option can possibly take
+         """
+         if not isinstance(values, tuple):
+             raise TypeError(_('values must be a tuple for {0}').format(name))
 -        self._open_values = open_values
+         if open_values not in (True, False):
+             raise TypeError(_('open_values must be a boolean for '
+                             '{0}').format(name))
 -        return self._values
++        self._extra = {'_choice_open_values': open_values,
++                       '_choice_values': values}
+         super(ChoiceOption, self).__init__(name, doc, default=default,
+                                            default_multi=default_multi,
+                                            callback=callback,
+                                            callback_params=callback_params,
+                                            requires=requires,
+                                            multi=multi,
+                                            validator=validator,
+                                            validator_params=validator_params,
+                                            properties=properties,
+                                            warnings_only=warnings_only)
+     def impl_get_values(self):
 -        return self._open_values
++        return self._extra['_choice_values']
+     def impl_is_openvalues(self):
 -                               '').format(value, self._values))
++        return self._extra['_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'
 -    _opt_type = 'bool'
++                               '').format(value, self._extra['_choice_values']))
+ class BoolOption(Option):
+     "represents a choice between ``True`` and ``False``"
+     __slots__ = tuple()
 -    _opt_type = 'int'
+     def _validate(self, value):
+         if not isinstance(value, bool):
+             raise ValueError(_('invalid boolean'))
+ class IntOption(Option):
+     "represents a choice of an integer"
+     __slots__ = tuple()
 -    _opt_type = 'float'
+     def _validate(self, value):
+         if not isinstance(value, int):
+             raise ValueError(_('invalid integer'))
+ class FloatOption(Option):
+     "represents a choice of a floating point number"
+     __slots__ = tuple()
 -    _opt_type = 'string'
+     def _validate(self, value):
+         if not isinstance(value, float):
+             raise ValueError(_('invalid float'))
+ class StrOption(Option):
+     "represents the choice of a string"
+     __slots__ = tuple()
 -        _opt_type = 'unicode'
+     def _validate(self, value):
+         if not isinstance(value, str):
+             raise ValueError(_('invalid string'))
+ if sys.version_info[0] >= 3:
+     #UnicodeOption is same as StrOption in python 3+
+     class UnicodeOption(StrOption):
+         __slots__ = tuple()
+         pass
+ else:
+     class UnicodeOption(Option):
+         "represents the choice of a unicode string"
+         __slots__ = tuple()
 -    __slots__ = ('_private_only', '_allow_reserved')
 -    _opt_type = 'ip'
+         _empty = u''
+         def _validate(self, value):
+             if not isinstance(value, unicode):
+                 raise ValueError(_('invalid unicode'))
+ class IPOption(Option):
+     "represents the choice of an ip"
 -        self._private_only = private_only
 -        self._allow_reserved = allow_reserved
++    __slots__ = tuple()
+     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, private_only=False, allow_reserved=False,
+                  warnings_only=False):
 -        if not self._allow_reserved and ip.iptype() == 'RESERVED':
++        self._extra = {'_private_only': private_only, '_allow_reserved': allow_reserved}
+         super(IPOption, self).__init__(name, doc, default=default,
+                                        default_multi=default_multi,
+                                        callback=callback,
+                                        callback_params=callback_params,
+                                        requires=requires,
+                                        multi=multi,
+                                        validator=validator,
+                                        validator_params=validator_params,
+                                        properties=properties,
+                                        warnings_only=warnings_only)
+     def _validate(self, value):
+         # sometimes an ip term starts with a zero
+         # but this does not fit in some case, for example bind does not like it
+         try:
+             for val in value.split('.'):
+                 if val.startswith("0") and len(val) > 1:
+                     raise ValueError(_('invalid IP'))
+         except AttributeError:
+             #if integer for example
+             raise ValueError(_('invalid IP'))
+         # 'standard' validation
+         try:
+             IP('{0}/32'.format(value))
+         except ValueError:
+             raise ValueError(_('invalid IP'))
+     def _second_level_validation(self, value, warnings_only):
+         ip = IP('{0}/32'.format(value))
 -        if self._private_only and not ip.iptype() == 'PRIVATE':
++        if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED':
+             if warnings_only:
+                 msg = _("IP is in reserved class")
+             else:
+                 msg = _("invalid IP, mustn't be in reserved class")
+             raise ValueError(msg)
 -            raise ValueError(msg.format(ip, opts[0]._name, network,
 -                             opts[1]._name, netmask, opts[2]._name))
++        if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE':
+             if warnings_only:
+                 msg = _("IP is not in private class")
+             else:
+                 msg = _("invalid IP, must be in private class")
+             raise ValueError(msg)
+     def _cons_in_network(self, opts, vals, warnings_only):
+         if len(vals) != 3:
+             raise ConfigError(_('invalid len for vals'))
+         if None in vals:
+             return
+         ip, network, netmask = vals
+         if IP(ip) not in IP('{0}/{1}'.format(network, netmask)):
+             if warnings_only:
+                 msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
+                         ' ({5})')
+             else:
+                 msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with '
+                         'netmask {4} ({5})')
 -    __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
 -    _opt_type = 'port'
++            raise ValueError(msg.format(ip, opts[0].impl_getname(), network,
++                             opts[1].impl_getname(), netmask, opts[2].impl_getname()))
+ class PortOption(Option):
+     """represents the choice of a port
+     The port numbers are divided into three ranges:
+     the well-known ports,
+     the registered ports,
+     and the dynamic or private ports.
+     You can actived this three range.
+     Port number 0 is reserved and can't be used.
+     see: http://en.wikipedia.org/wiki/Port_numbers
+     """
 -        self._allow_range = allow_range
 -        self._min_value = None
 -        self._max_value = None
++    __slots__ = tuple()
+     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):
 -            if self._min_value is 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
+         for index, allowed in enumerate([allow_zero,
+                                          allow_wellknown,
+                                          allow_registred,
+                                          allow_private]):
 -                    self._min_value = ports_min[index]
++            if extra['_min_value'] is None:
+                 if allowed:
 -                self._max_value = ports_max[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:
 -        if self._max_value is None:
++                extra['_max_value'] = ports_max[index]
 -        if self._allow_range and ":" in str(value):
++        if extra['_max_value'] is None:
+             raise ValueError(_('max value is empty'))
++        self._extra = extra
+         super(PortOption, self).__init__(name, doc, default=default,
+                                          default_multi=default_multi,
+                                          callback=callback,
+                                          callback_params=callback_params,
+                                          requires=requires,
+                                          multi=multi,
+                                          validator=validator,
+                                          validator_params=validator_params,
+                                          properties=properties,
+                                          warnings_only=warnings_only)
+     def _validate(self, value):
 -                int(val)
++        if self._extra['_allow_range'] and ":" in str(value):
+             value = str(value).split(':')
+             if len(value) != 2:
+                 raise ValueError(_('invalid port, range must have two values '
+                                  'only'))
+             if not value[0] < value[1]:
+                 raise ValueError(_('invalid port, first port in range must be'
+                                  ' smaller than the second one'))
+         else:
+             value = [value]
+         for val in value:
+             try:
 -            if not self._min_value <= int(val) <= self._max_value:
++                val = int(val)
+             except ValueError:
+                 raise ValueError(_('invalid port'))
 -                                   'and {1}').format(self._min_value,
 -                                                     self._max_value))
++            if not self._extra['_min_value'] <= val <= self._extra['_max_value']:
+                 raise ValueError(_('invalid port, must be an between {0} '
 -    _opt_type = 'network'
++                                   'and {1}').format(self._extra['_min_value'],
++                                                     self._extra['_max_value']))
+ class NetworkOption(Option):
+     "represents the choice of a network"
+     __slots__ = tuple()
 -    _opt_type = 'netmask'
+     def _validate(self, value):
+         try:
+             IP(value)
+         except ValueError:
+             raise ValueError(_('invalid network address'))
+     def _second_level_validation(self, value, warnings_only):
+         ip = IP(value)
+         if ip.iptype() == 'RESERVED':
+             if warnings_only:
+                 msg = _("network address is in reserved class")
+             else:
+                 msg = _("invalid network address, mustn't be in reserved class")
+             raise ValueError(msg)
+ class NetmaskOption(Option):
+     "represents the choice of a netmask"
+     __slots__ = tuple()
 -            raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
+     def _validate(self, value):
+         try:
+             IP('0.0.0.0/{0}'.format(value))
+         except ValueError:
+             raise ValueError(_('invalid netmask address'))
+     def _cons_network_netmask(self, opts, vals, warnings_only):
+         #opts must be (netmask, network) options
+         if None in vals:
+             return
+         self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
+     def _cons_ip_netmask(self, opts, vals, warnings_only):
+         #opts must be (netmask, ip) options
+         if None in vals:
+             return
+         self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only)
+     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
+                        warnings_only):
+         if len(opts) != 2:
+             raise ConfigError(_('invalid len for opts'))
+         msg = None
+         try:
+             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
+                     make_net=make_net)
+             #if cidr == 32, ip same has network
+             if ip.prefixlen() != 32:
+                 try:
+                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
+                         make_net=not make_net)
+                 except ValueError:
+                     pass
+                 else:
+                     if make_net:
+                         msg = _("invalid IP {0} ({1}) with netmask {2},"
+                                 " this IP is a network")
+         except ValueError:
+             if not make_net:
+                 msg = _('invalid network {0} ({1}) with netmask {2}')
+         if msg is not None:
 -    _opt_type = 'broadcast'
++            raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
+                                         val_netmask))
+ class BroadcastOption(Option):
+     __slots__ = tuple()
 -                                   broadcast, opts[0]._name, network,
 -                                   opts[1]._name, netmask, opts[2]._name))
+     def _validate(self, value):
+         try:
+             IP('{0}/32'.format(value))
+         except ValueError:
+             raise ValueError(_('invalid broadcast address'))
+     def _cons_broadcast(self, opts, vals, warnings_only):
+         if len(vals) != 3:
+             raise ConfigError(_('invalid len for vals'))
+         if None in vals:
+             return
+         broadcast, network, netmask = vals
+         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(
 -    __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
 -    _opt_type = 'domainname'
++                                   broadcast, opts[0].impl_getname(), network,
++                                   opts[1].impl_getname(), netmask, opts[2].impl_getname()))
+ class DomainnameOption(Option):
+     """represents the choice of a domain name
+     netbios: for MS domain
+     hostname: to identify the device
+     domainname:
+     fqdn: with tld, not supported yet
+     """
 -        self._type = type_
++    __slots__ = tuple()
+     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_ip=False, type_='domainname',
+                  warnings_only=False, allow_without_dot=False):
+         if type_ not in ['netbios', 'hostname', 'domainname']:
+             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
 -        self._allow_ip = allow_ip
 -        self._allow_without_dot = allow_without_dot
++        self._extra = {'_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]:
+             raise ValueError(_('allow_without_dot must be a boolean'))
 -        if self._type != 'netbios':
++        self._extra['_allow_ip'] = allow_ip
++        self._extra['_allow_without_dot'] = allow_without_dot
+         end = ''
+         extrachar = ''
+         extrachar_mandatory = ''
 -        if self._type == 'netbios':
++        if self._extra['_dom_type'] != 'netbios':
+             allow_number = '\d'
+         else:
+             allow_number = ''
 -        elif self._type == 'hostname':
++        if self._extra['_dom_type'] == 'netbios':
+             length = 14
 -        elif self._type == 'domainname':
++        elif self._extra['_dom_type'] == 'hostname':
+             length = 62
 -        self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
 -                                     ''.format(allow_number, extrachar, length,
 -                                               extrachar_mandatory, end))
++        elif self._extra['_dom_type'] == 'domainname':
+             length = 62
+             if allow_without_dot is False:
+                 extrachar_mandatory = '\.'
+             else:
+                 extrachar = '\.'
+             end = '+[a-z]*'
 -        if self._allow_ip is True:
++        self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
++                                               ''.format(allow_number, extrachar, length,
++                                                         extrachar_mandatory, end))
+         super(DomainnameOption, self).__init__(name, doc, default=default,
+                                                default_multi=default_multi,
+                                                callback=callback,
+                                                callback_params=callback_params,
+                                                requires=requires,
+                                                multi=multi,
+                                                validator=validator,
+                                                validator_params=validator_params,
+                                                properties=properties,
+                                                warnings_only=warnings_only)
+     def _validate(self, value):
 -        if self._type == 'domainname' and not self._allow_without_dot and \
++        if self._extra['_allow_ip'] is True:
+             try:
+                 IP('{0}/32'.format(value))
+                 return
+             except ValueError:
+                 pass
 -        if not self._domain_re.search(value):
++        if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \
+                 '.' not in value:
+             raise ValueError(_("invalid domainname, must have dot"))
+         if len(value) > 255:
+             raise ValueError(_("invalid domainname's length (max 255)"))
+         if len(value) < 2:
+             raise ValueError(_("invalid domainname's length (min 2)"))
 -    _opt_type = 'email'
++        if not self._extra['_domain_re'].search(value):
+             raise ValueError(_('invalid domainname'))
+ class EmailOption(DomainnameOption):
+     __slots__ = tuple()
 -    _opt_type = 'url'
+     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
+     def _validate(self, value):
+         splitted = value.split('@', 1)
+         try:
+             username, domain = splitted
+         except ValueError:
+             raise ValueError(_('invalid email address, must contains one @'
+                                ))
+         if not self.username_re.search(username):
+             raise ValueError(_('invalid username in email address'))
+         super(EmailOption, self)._validate(domain)
+ class URLOption(DomainnameOption):
+     __slots__ = tuple()
 -    _opt_type = 'username'
+     proto_re = re.compile(r'(http|https)://')
+     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
+     def _validate(self, value):
+         match = self.proto_re.search(value)
+         if not match:
+             raise ValueError(_('invalid url, must start with http:// or '
+                                'https://'))
+         value = value[len(match.group(0)):]
+         # get domain/files
+         splitted = value.split('/', 1)
+         try:
+             domain, files = splitted
+         except ValueError:
+             domain = value
+             files = None
+         # if port in domain
+         splitted = domain.split(':', 1)
+         try:
+             domain, port = splitted
+         except ValueError:
+             domain = splitted[0]
+             port = 0
+         if not 0 <= int(port) <= 65535:
+             raise ValueError(_('invalid url, port must be an between 0 and '
+                                '65536'))
+         # validate domainname
+         super(URLOption, self)._validate(domain)
+         # validate file
+         if files is not None and files != '' and not self.path_re.search(files):
+             raise ValueError(_('invalid url, must ends with filename'))
+ class UsernameOption(Option):
+     __slots__ = tuple()
 -    _opt_type = 'file'
+     #regexp build with 'man 8 adduser' informations
+     username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
+     def _validate(self, value):
+         match = self.username_re.search(value)
+         if not match:
+             raise ValueError(_('invalid username'))
+ class FilenameOption(Option):
+     __slots__ = tuple()
+     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
+     def _validate(self, value):
+         match = self.path_re.search(value)
+         if not match:
+             raise ValueError(_('invalid filename'))
index 0000000,8c26f3b..e692458
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,263 +1,235 @@@
 -class OptionDescription(BaseOption):
+ # -*- coding: utf-8 -*-
+ # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
+ #
+ # This program is free software: you can redistribute it and/or modify it
+ # under the terms of the GNU Lesser General Public License as published by the
+ # Free Software Foundation, either version 3 of the License, or (at your
+ # option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful, but WITHOUT
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+ # details.
+ #
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ #
+ # The original `Config` design model is unproudly borrowed from
+ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+ # the whole pypy projet is under MIT licence
+ # ____________________________________________________________
+ from copy import copy
+ from tiramisu.i18n import _
+ from tiramisu.setting import groups, log
+ from .baseoption import BaseOption
+ from . import MasterSlaves
+ from tiramisu.error import ConfigError, ConflictError, ValueWarning
++from tiramisu.storage import get_storages_option
 -    __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'
++StorageOptionDescription = get_storages_option('optiondescription')
++
++
++class OptionDescription(BaseOption, StorageOptionDescription):
+     """Config's schema (organisation, group) and container of Options
+     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
+     """
 -        super(OptionDescription, self).__init__(name, doc, requires, properties)
 -        child_names = [child._name for child in children]
++    __slots__ = tuple()
+     def __init__(self, name, doc, children, requires=None, properties=None):
+         """
+         :param children: a list of options (including optiondescriptions)
+         """
 -        self._children = (tuple(child_names), tuple(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()
+         old = None
+         for child in valid_child:
+             if child == old:
+                 raise ConflictError(_('duplicate option name: '
+                                       '{0}').format(child))
+             old = child
 -    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:
 -            log.debug('__getattr__', exc_info=True)
 -            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()])
++        self._add_children(child_names, 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._is_build_cache = False
++
++    def impl_getrequires(self):
++        return self._requires
+     def impl_getdoc(self):
+         return self.impl_get_information('doc')
 -            attr = option._name
++    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
+            _currpath should not be provided (helps with recursion)
+         """
+         if _currpath is None:
+             _currpath = []
+         paths = []
+         for option in self.impl_getchildren():
 -    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 = []
++            attr = option.impl_getname()
+             if isinstance(option, OptionDescription):
+                 if include_groups:
+                     paths.append('.'.join(_currpath + [attr]))
+                 paths += option.impl_getpaths(include_groups=include_groups,
+                                               _currpath=_currpath + [attr])
+             else:
+                 paths.append('.'.join(_currpath + [attr]))
+         return paths
 -            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])))
++    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():
 -                if not force_no_consistencies and \
 -                        option._consistencies is not None:
 -                    for consistency in option._consistencies:
 -                        func, all_cons_opts, params = consistency
 -                        for opt in all_cons_opts:
 -                            _consistencies.setdefault(opt,
 -                                                      []).append((func,
 -                                                                  all_cons_opts,
 -                                                                  params))
++            cache_option.append(option._get_id())
+             if not isinstance(option, OptionDescription):
 -                _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))
++                for func, all_cons_opts, params in option._get_consistencies():
++                    for opt in all_cons_opts:
++                        _consistencies.setdefault(opt,
++                                                  []).append((func,
++                                                             all_cons_opts,
++                                                             params))
+             else:
 -                MasterSlaves(self._name, self.impl_getchildren())
++                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._get_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._get_id() in cache_option:
++                raise ConflictError(_('duplicate option: {0}').format(option))
++            cache_option.append(option._get_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):
+         """sets a given group object to an OptionDescription
+         :param group_type: an instance of `GroupType` or `MasterGroupType`
+                               that lives in `setting.groups`
+         """
+         if self._group_type != groups.default:
+             raise TypeError(_('cannot change group_type if already set '
+                             '(old {0}, new {1})').format(self._group_type,
+                                                          group_type))
+         if isinstance(group_type, groups.GroupType):
+             self._group_type = group_type
+             if isinstance(group_type, groups.MasterGroupType):
++                MasterSlaves(self.impl_getname(), self.impl_getchildren())
+         else:
+             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
+         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
+         consistencies = self._cache_consistencies.get(option)
+         if consistencies is not None:
+             for func, all_cons_opts, params in consistencies:
+                 warnings_only = params.get('warnings_only', False)
+                 #all_cons_opts[0] is the option where func is set
+                 try:
+                     all_cons_opts[0]._launch_consistency(func, option,
+                                                          value,
+                                                          context, index,
+                                                          all_cons_opts,
+                                                          warnings_only)
+                 except ValueError as err:
+                     if warnings_only:
+                         raise ValueWarning(err.message, option)
+                     else:
+                         raise err
+     def _impl_getstate(self, descr=None):
+         """enables us to export into a dict
+         :param descr: parent :class:`tiramisu.option.OptionDescription`
+         """
+         if descr is None:
+             self.impl_build_cache()
+             descr = self
+         super(OptionDescription, self)._impl_getstate(descr)
+         self._state_group_type = str(self._group_type)
+         for option in self.impl_getchildren():
+             option._impl_getstate(descr)
+     def __getstate__(self):
+         """special method to enable the serialization with pickle
+         """
+         stated = True
+         try:
+             # the `_state` attribute is a flag that which tells us if
+             # the serialization can be performed
+             self._stated
+         except AttributeError:
+             # if cannot delete, _impl_getstate never launch
+             # launch it recursivement
+             # _stated prevent __getstate__ launch more than one time
+             # _stated is delete, if re-serialize, re-lauch _impl_getstate
+             self._impl_getstate()
+             stated = False
+         return super(OptionDescription, self).__getstate__(stated)
+     def _impl_setstate(self, descr=None):
+         """enables us to import from a dict
+         :param descr: parent :class:`tiramisu.option.OptionDescription`
+         """
+         if descr is None:
+             self._cache_paths = None
+             self._cache_consistencies = None
+             self.impl_build_cache(force_no_consistencies=True)
+             descr = self
+         self._group_type = getattr(groups, self._state_group_type)
+         del(self._state_group_type)
+         super(OptionDescription, self)._impl_setstate(descr)
+         for option in self.impl_getchildren():
+             option._impl_setstate(descr)
+     def __setstate__(self, state):
+         super(OptionDescription, self).__setstate__(state)
+         try:
+             self._stated
+         except AttributeError:
+             self._impl_setstate()
@@@ -212,28 -214,8 +214,8 @@@ def populate_owners()
      setattr(owners, 'addowner', addowner)
  
  
- def populate_multitypes():
-     """all multi option should have a type, this type is automaticly set do
-     not touch this
-     default
-         default's multi option set if not master or slave
-     master
-         master's option in a group with master's type, name of this option
-         should be the same name of the optiondescription
-     slave
-         slave's option in a group with master's type
-     """
-     setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
-     setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
-     setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
  # ____________________________________________________________
--# populate groups, owners and multitypes with default attributes
++# populate groups and owners with default attributes
  groups = GroupModule()
  populate_groups()
  owners = OwnerModule()
@@@ -120,21 -108,13 +115,24 @@@ def get_storages(context, session_id, p
      return imp.Settings(storage), imp.Values(storage)
  
  
- def get_storages_option():
 -def list_sessions():
++def get_storages_option(type_):
 +    imp = storage_option_type.get()
-     return imp.Base, imp.OptionDescription
++    if type_ == 'base':
++        return imp.Base
++    else:
++        return imp.OptionDescription
 +
 +
 +def list_sessions(type_):
      """List all available session (persistent or not persistent)
      """
 -    return storage_type.get().list_sessions()
 +    if type_ == 'option':
 +        return storage_option_type.get().list_sessions()
 +    else:
 +        return storage_type.get().list_sessions()
  
  
 -def delete_session(session_id):
 +def delete_session(type_, session_id):
      """Delete a selected session, be careful, you can deleted a session
      use by an other instance
      :params session_id: id of session to delete
@@@ -1,21 -1,19 +1,19 @@@
  # -*- coding: utf-8 -*-
 -"default plugin for cache: set it in a simple dictionary"
 +"utils used by storage"
  # Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
  #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
+ # This program is free software: you can redistribute it and/or modify it
+ # under the terms of the GNU Lesser General Public License as published by the
+ # Free Software Foundation, either version 3 of the License, or (at your
+ # option) any later version.
  #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ # This program is distributed in the hope that it will be useful, but WITHOUT
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+ # details.
  #
+ # You should have received a copy of the GNU Lesser General Public License
+ # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  # ____________________________________________________________
  from tiramisu.setting import owners
  
@@@ -57,36 -54,65 +54,65 @@@ class Values(object)
              raise ConfigError(_('the context does not exist anymore'))
          return context
  
-     def _getdefault(self, opt):
-         """
-         actually retrieves the default value
+     def _getvalue(self, opt, path, is_default, index=undefined):
+         """actually retrieves the value
  
          :param opt: the `option.Option()` object
+         :returns: the option's value (or the default value if not set)
          """
 -            callback, callback_params = opt._callback
+         setting = self._getcontext().cfgimpl_get_settings()
+         force_default = 'frozen' in setting[opt] and \
+             'force_default_on_freeze' in setting[opt]
+         if not is_default and not force_default:
+             value = self._p_.getvalue(path)
+             if index is not undefined:
+                 try:
+                     return value[index]
+                 except IndexError:
+                     #value is smaller than expected
+                     #so return default value
+                     pass
+             else:
+                 return value
+         #so default value
+         # if value has callback and is not set
+         if opt.impl_has_callback():
++            callback, callback_params = opt.impl_get_callback()
+             if callback_params is None:
+                 callback_params = {}
+             value = carry_out_calculation(opt, config=self._getcontext(),
+                                           callback=callback,
+                                           callback_params=callback_params,
+                                           index=index)
+             try:
+                 if isinstance(value, list) and index is not undefined:
+                     return value[index]
+                 return value
+             except IndexError:
+                 pass
          meta = self._getcontext().cfgimpl_get_meta()
          if meta is not None:
+             #FIXME : problème de longueur si meta + slave
+             #doit passer de meta à pas meta
+             #en plus il faut gérer la longueur avec les meta !
+             #FIXME SymlinkOption
              value = meta.cfgimpl_get_values()[opt]
              if isinstance(value, Multi):
-                 value = list(value)
-         else:
-             value = opt.impl_getdefault()
-         if opt.impl_is_multi():
-             return copy(value)
-         else:
+                 if index is not undefined:
+                     value = value[index]
+                 else:
+                     value = list(value)
              return value
-     def _getvalue(self, opt, path):
-         """actually retrieves the value
-         :param opt: the `option.Option()` object
-         :returns: the option's value (or the default value if not set)
-         """
-         if not self._p_.hasvalue(path):
-             # if there is no value
-             value = self._getdefault(opt)
-         else:
-             # if there is a value
-             value = self._p_.getvalue(path)
+         # now try to get default value
+         value = opt.impl_getdefault()
+         if opt.impl_is_multi() and index is not undefined:
+             if value is None:
+                 value = opt.impl_getdefault_multi()
+             else:
+                 try:
+                     value = value[index]
+                 except IndexError:
+                     value = opt.impl_getdefault_multi()
          return value
  
      def get_modified_values(self):
@@@ -420,10 -478,9 +478,9 @@@ class Multi(list)
          :param value: the Multi wraps a list value
          :param context: the home config that has the values
          :param opt: the option object that have this Multi value
-         :param setitem: only if set a value
          """
          if isinstance(value, Multi):
--            raise ValueError(_('{0} is already a Multi ').format(opt._name))
++            raise ValueError(_('{0} is already a Multi ').format(opt.impl_getname()))
          self.opt = opt
          self.path = path
          if not isinstance(context, weakref.ReferenceType):
          """the list value can be updated (appened)
          only if the option is a master
          """
-         context = self._getcontext()
+         values = self._getcontext().cfgimpl_get_values()
          if not force:
-             if self.opt.impl_get_multitype() == multitypes.slave:
+             if self.opt.impl_is_master_slaves('slave'):
                  raise SlaveError(_("cannot append a value on a multi option {0}"
 -                                   " which is a slave").format(self.opt._name))
 +                                   " which is a slave").format(self.opt.impl_getname()))
-             elif self.opt.impl_get_multitype() == multitypes.master:
-                 values = context.cfgimpl_get_values()
-                 if value is undefined and self.opt.impl_has_callback():
-                     value = values._getcallback_value(self.opt)
-                     #Force None il return a list
-                     if isinstance(value, list):
-                         value = None
          index = self.__len__()
          if value is undefined:
-             value = self.opt.impl_getdefault_multi()
+             try:
+                 value = values._get_validated_value(self.opt, self.path,
+                                                     True, False, None, True,
+                                                     index=index)
+             except IndexError:
+                 value = None
          self._validate(value, index)
          super(Multi, self).append(value)
-         context.cfgimpl_get_values()._setvalue(self.opt, self.path,
-                                                self,
-                                                validate_properties=not force)
-         if not force and self.opt.impl_get_multitype() == multitypes.master:
-             for slave in self.opt.impl_get_master_slaves():
-                 path = values._get_opt_path(slave)
-                 if not values._is_default_owner(path):
-                     if slave.impl_has_callback():
-                         dvalue = values._getcallback_value(slave, index=index)
-                     else:
-                         dvalue = slave.impl_getdefault_multi()
-                     old_value = values.getitem(slave, path, validate=False,
-                                                validate_properties=False)
-                     if len(old_value) + 1 != self.__len__():
-                         raise SlaveError(_("invalid len for the slave: {0}"
-                                            " which has {1} as master").format(
-                                                self.opt._name, self.__len__()))
-                     values.getitem(slave, path, validate=False,
-                                    validate_properties=False).append(
-                                        dvalue, force=True)
+         if setitem:
+             values._setvalue(self.opt, self.path, self,
+                              validate_properties=not force)
  
      def sort(self, cmp=None, key=None, reverse=False):
-         if self.opt.impl_get_multitype() in [multitypes.slave,
-                                              multitypes.master]:
+         if self.opt.impl_is_master_slaves():
              raise SlaveError(_("cannot sort multi option {0} if master or slave"
 -                               "").format(self.opt._name))
 +                               "").format(self.opt.impl_getname()))
          if sys.version_info[0] >= 3:
              if cmp is not None:
-                 raise ValueError(_('cmp is not permitted in python v3 or greater'))
+                 raise ValueError(_('cmp is not permitted in python v3 or '
+                                    'greater'))
              super(Multi, self).sort(key=key, reverse=reverse)
          else:
              super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
-         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                           self)
  
      def reverse(self):
-         if self.opt.impl_get_multitype() in [multitypes.slave,
-                                              multitypes.master]:
+         if self.opt.impl_is_master_slaves():
              raise SlaveError(_("cannot reverse multi option {0} if master or "
 -                               "slave").format(self.opt._name))
 +                               "slave").format(self.opt.impl_getname()))
          super(Multi, self).reverse()
-         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                           self)
  
      def insert(self, index, obj):
-         if self.opt.impl_get_multitype() in [multitypes.slave,
-                                              multitypes.master]:
+         if self.opt.impl_is_master_slaves():
              raise SlaveError(_("cannot insert multi option {0} if master or "
 -                               "slave").format(self.opt._name))
 +                               "slave").format(self.opt.impl_getname()))
          super(Multi, self).insert(index, obj)
-         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                           self)
  
      def extend(self, iterable):
-         if self.opt.impl_get_multitype() in [multitypes.slave,
-                                              multitypes.master]:
+         if self.opt.impl_is_master_slaves():
              raise SlaveError(_("cannot extend multi option {0} if master or "
 -                               "slave").format(self.opt._name))
 +                               "slave").format(self.opt.impl_getname()))
          super(Multi, self).extend(iterable)
-         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+         self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                           self)
  
      def _validate(self, value, force_index):
          if value is not None:
          """
          context = self._getcontext()
          if not force:
-             if self.opt.impl_get_multitype() == multitypes.slave:
+             if self.opt.impl_is_master_slaves('slave'):
                  raise SlaveError(_("cannot pop a value on a multi option {0}"
 -                                   " which is a slave").format(self.opt._name))
 +                                   " which is a slave").format(self.opt.impl_getname()))
-             elif self.opt.impl_get_multitype() == multitypes.master:
-                 for slave in self.opt.impl_get_master_slaves():
-                     values = context.cfgimpl_get_values()
-                     if not values.is_default_owner(slave):
-                         #get multi without valid properties
-                         values.getitem(slave, validate=False,
-                                        validate_properties=False
-                                        ).pop(index, force=True)
+             if self.opt.impl_is_master_slaves('master'):
+                 self.opt.impl_get_master_slaves().pop(
+                     context.cfgimpl_get_values(), index)
          #set value without valid properties
          ret = super(Multi, self).pop(index)
-         context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
+         context.cfgimpl_get_values()._setvalue(self.opt, self.path, self,
+                                                validate_properties=not force)
          return ret