_calc_requirement is a set, not a tuple
[tiramisu.git] / tiramisu / config.py
index 811f931..dd0b0f9 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-"pretty small and local configuration management tool"
+"options handler global entry point"
 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
 #
 # This program is free software; you can redistribute it and/or modify
 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
-#from inspect import getmembers, ismethod
-from tiramisu.error import (PropertiesOptionError, NotFoundError,
-                            AmbigousOptionError, NoMatchingOptionFound, MandatoryError)
-from tiramisu.option import OptionDescription, Option, SymLinkOption
-from tiramisu.setting import groups, Setting, apply_requires
+from time import time
+from tiramisu.error import PropertiesOptionError, ConfigError
+from tiramisu.option import OptionDescription, Option, SymLinkOption, \
+    BaseInformation
+from tiramisu.setting import groups, Settings, default_encoding, storage_type
 from tiramisu.value import Values
+from tiramisu.i18n import _
 
 
-class SubConfig(object):
+def gen_id(config):
+    return str(id(config)) + str(time())
+
+
+class SubConfig(BaseInformation):
     "sub configuration management entry"
-    __slots__ = ('_cfgimpl_descr', '_cfgimpl_context')
+    __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
 
-    def __init__(self, descr, context):
+    def __init__(self, descr, context, subpath=None):
         """ Configuration option management master class
 
         :param descr: describes the configuration schema
         :type descr: an instance of ``option.OptionDescription``
         :param context: the current root config
         :type context: `Config`
+        :type subpath: `str` with the path name
         """
         # main option description
-        self._cfgimpl_descr = descr
+        if not isinstance(descr, OptionDescription):
+            raise ValueError(_('descr must be an optiondescription, not {0}'
+                               ).format(type(descr)))
+        self._impl_descr = descr
         # sub option descriptions
-        self._cfgimpl_context = context
-
-    def cfgimpl_get_context(self):
-        return self._cfgimpl_context
-
-    def cfgimpl_get_settings(self):
-        return self._cfgimpl_context._cfgimpl_settings
-
-    def cfgimpl_get_values(self):
-        return self._cfgimpl_context._cfgimpl_values
-
-    def cfgimpl_get_consistancies(self):
-        return self.cfgimpl_get_context().cfgimpl_get_description()._consistancies
-
-    def cfgimpl_get_description(self):
-        return self._cfgimpl_descr
-
-    # ____________________________________________________________
-    # attribute methods
-    def __setattr__(self, name, value):
-        "attribute notation mechanism for the setting of the value of an option"
-        if name.startswith('_cfgimpl_'):
-            #self.__dict__[name] = value
-            object.__setattr__(self, name, value)
-            return
-        self._setattr(name, value)
-
-    def _setattr(self, name, value, force_permissive=False):
-        if '.' in name:
-            homeconfig, name = self.cfgimpl_get_home_by_path(name)
-            return homeconfig.__setattr__(name, value)
-        child = getattr(self._cfgimpl_descr, name)
-        if type(child) != SymLinkOption:
-            self._validate(name, getattr(self._cfgimpl_descr, name), force_permissive=force_permissive)
-            self.setoption(name, child, value)
-        else:
-            child.setoption(self.cfgimpl_get_context(), value)
-
-    def _validate(self, name, opt_or_descr, force_permissive=False):
-        "validation for the setattr and the getattr"
-        if not isinstance(opt_or_descr, Option) and \
-                not isinstance(opt_or_descr, OptionDescription):
-            raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
-        properties = set(self.cfgimpl_get_settings().get_properties(opt_or_descr))
-        #remove this properties, those properties are validate in value/setting
-        properties = properties - set(['mandatory', 'frozen'])
-        set_properties = set(self.cfgimpl_get_settings().get_properties())
-        properties = properties & set_properties
-        if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive', is_apply_req=False):
-            properties = properties - set(self.cfgimpl_get_settings().get_permissive())
-        properties = properties - set(self.cfgimpl_get_settings().get_permissive(opt_or_descr))
-        properties = list(properties)
-        if properties != []:
-            raise PropertiesOptionError("trying to access"
-                                        " to an option named: {0} with properties"
-                                        " {1}".format(name, str(properties)),
-                                        properties)
+        if not isinstance(context, SubConfig):
+            raise ValueError('context must be a SubConfig')
+        self._impl_context = context
+        self._impl_path = subpath
 
-    def __getattr__(self, name):
-        return self._getattr(name)
+    def cfgimpl_reset_cache(self, only_expired=False, only=('values',
+                                                            'settings')):
+        self.cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
 
-    def _getattr(self, name, force_permissive=False, force_properties=None,
-                 validate=True):
-        """
-        attribute notation mechanism for accessing the value of an option
-        :param name: attribute name
-        :return: option's value if name is an option name, OptionDescription
-                 otherwise
-        """
-        # attribute access by passing a path,
-        # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
-        if '.' in name:
-            homeconfig, name = self.cfgimpl_get_home_by_path(name,
-                                                             force_permissive=force_permissive,
-                                                             force_properties=force_properties)
-            return homeconfig._getattr(name, force_permissive=force_permissive,
-                                       force_properties=force_properties,
-                                       validate=validate)
-        opt_or_descr = getattr(self._cfgimpl_descr, name)
-        # symlink options
-        if type(opt_or_descr) == SymLinkOption:
-            rootconfig = self.cfgimpl_get_context()
-            path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt)
-            return rootconfig._getattr(path, validate=validate)
-        self._validate(name, opt_or_descr, force_permissive=force_permissive)
-        if isinstance(opt_or_descr, OptionDescription):
-            children = self.cfgimpl_get_description()._children
-            if opt_or_descr not in children[1]:
-                raise AttributeError("{0} with name {1} object has "
-                                     "no attribute {2}".format(self.__class__,
-                                                               opt_or_descr._name,
-                                                               name))
-            return SubConfig(opt_or_descr, self._cfgimpl_context)
-        # special attributes
-        if name.startswith('_cfgimpl_'):
-            # if it were in __dict__ it would have been found already
-            object.__getattr__(self, name)
-        return self.cfgimpl_get_values()._getitem(opt_or_descr,
-                                                  force_properties=force_properties,
-                                                  validate=validate)
-
-    def setoption(self, name, child, value):
-        """effectively modifies the value of an Option()
-        (typically called by the __setattr__)
-        """
-        setting = self.cfgimpl_get_settings()
-        #needed ?
-        apply_requires(child, self)
-        #needed to ?
-        if child not in self._cfgimpl_descr._children[1]:
-            raise AttributeError('unknown option %s' % (name))
-
-        if setting.has_property('everything_frozen'):
-            raise TypeError("cannot set a value to the option {} if the whole "
-                            "config has been frozen".format(name))
-
-        if setting.has_property('frozen') and setting.has_property('frozen',
-                                                                   child, is_apply_req=False):
-            raise TypeError('cannot change the value to %s for '
-                            'option %s this option is frozen' % (str(value), name))
-        self.cfgimpl_get_values()[child] = value
-
-    def cfgimpl_get_home_by_path(self, path, force_permissive=False, force_properties=None):
+    def cfgimpl_get_home_by_path(self, path, force_permissive=False,
+                                 force_properties=None):
         """:returns: tuple (config, name)"""
         path = path.split('.')
         for step in path[:-1]:
@@ -174,44 +71,44 @@ class SubConfig(object):
                                  force_properties=force_properties)
         return self, path[-1]
 
-    def getkey(self):
-        return self._cfgimpl_descr.getkey(self)
-
     def __hash__(self):
-        return hash(self.getkey())
+        return hash(self.cfgimpl_get_description().impl_getkey(self))
 
     def __eq__(self, other):
-        "Config comparison"
+        "Config's comparison"
         if not isinstance(other, Config):
             return False
-        return self.getkey() == other.getkey()
+        return self.cfgimpl_get_description().impl_getkey(self) == \
+            other.cfgimpl_get_description().impl_getkey(other)
 
     def __ne__(self, other):
-        "Config comparison"
+        "Config's comparison"
+        if not isinstance(other, Config):
+            return False
         return not self == other
 
     # ______________________________________________________________________
     def __iter__(self):
         """Pythonesque way of parsing group's ordered options.
         iteration only on Options (not OptionDescriptions)"""
-        for child in self._cfgimpl_descr._children[1]:
+        for child in self.cfgimpl_get_description().impl_getchildren():
             if not isinstance(child, OptionDescription):
                 try:
                     yield child._name, getattr(self, child._name)
                 except GeneratorExit:
                     raise StopIteration
-                except:
+                except PropertiesOptionError:
                     pass  # option with properties
 
     def iter_all(self):
         """A way of parsing options **and** groups.
         iteration on Options and OptionDescriptions."""
-        for child in self._cfgimpl_descr._children[1]:
+        for child in self.cfgimpl_get_description().impl_getchildren():
             try:
                 yield child._name, getattr(self, child._name)
             except GeneratorExit:
                 raise StopIteration
-            except:
+            except PropertiesOptionError:
                 pass  # option with properties
 
     def iter_groups(self, group_type=None):
@@ -222,22 +119,20 @@ class SubConfig(object):
         :param group_type: if defined, is an instance of `groups.GroupType`
                            or `groups.MasterGroupType` that lives in
                            `setting.groups`
-
         """
         if group_type is not None:
             if not isinstance(group_type, groups.GroupType):
-                raise TypeError("Unknown group_type: {0}".format(group_type))
-        for child in self._cfgimpl_descr._children[1]:
+                raise TypeError(_("unknown group_type: {0}").format(group_type))
+        for child in self.cfgimpl_get_description().impl_getchildren():
             if isinstance(child, OptionDescription):
                 try:
-                    if group_type is not None:
-                        if child.get_group_type() == group_type:
-                            yield child._name, getattr(self, child._name)
-                    else:
+                    if group_type is None or (group_type is not None and
+                                              child.impl_get_group_type()
+                                              == group_type):
                         yield child._name, getattr(self, child._name)
                 except GeneratorExit:
                     raise StopIteration
-                except:
+                except PropertiesOptionError:
                     pass
     # ______________________________________________________________________
 
@@ -245,45 +140,117 @@ class SubConfig(object):
         "Config's string representation"
         lines = []
         for name, grp in self.iter_groups():
-            lines.append("[%s]" % name)
+            lines.append("[{0}]".format(name))
         for name, value in self:
             try:
-                lines.append("%s = %s" % (name, value))
-            except:
+                lines.append("{0} = {1}".format(name, value))
+            except UnicodeEncodeError:
+                lines.append("{0} = {1}".format(name,
+                                                value.encode(default_encoding)))
+            except PropertiesOptionError:
                 pass
         return '\n'.join(lines)
 
     __repr__ = __str__
 
-    def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
-        """returns a list of all paths in self, recursively, taking care of
-        the context of properties (hidden/disabled)
+    def cfgimpl_get_context(self):
+        return self._impl_context
 
-        :param include_groups: if true, OptionDescription are included
-        :param allpaths: all the options (event the properties protected ones)
-        :param mandatory: includes the mandatory options
-        :returns: list of all paths
-        """
-        paths = []
-        for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
-            if allpaths:
-                paths.append(path)
+    def cfgimpl_get_description(self):
+        if self._impl_descr is None:
+            raise ConfigError(_('no option description found for this config'
+                                ' (may be metaconfig without meta)'))
+        else:
+            return self._impl_descr
+
+    def cfgimpl_get_settings(self):
+        return self.cfgimpl_get_context()._impl_settings
+
+    def cfgimpl_get_values(self):
+        return self.cfgimpl_get_context()._impl_values
+
+    # ____________________________________________________________
+    # attribute methods
+    def __setattr__(self, name, value):
+        "attribute notation mechanism for the setting of the value of an option"
+        if name.startswith('_impl_'):
+            #self.__dict__[name] = value
+            object.__setattr__(self, name, value)
+            return
+        self._setattr(name, value)
+
+    def _setattr(self, name, value, force_permissive=False):
+        if '.' in name:
+            homeconfig, name = self.cfgimpl_get_home_by_path(name)
+            return homeconfig.__setattr__(name, value)
+        child = getattr(self.cfgimpl_get_description(), name)
+        if not isinstance(child, SymLinkOption):
+            if self._impl_path is None:
+                path = name
             else:
-                try:
-                    getattr(self, path)
-                except MandatoryError:
-                    if mandatory:
-                        paths.append(path)
-                except PropertiesOptionError:
-                    pass
-                else:
-                    paths.append(path)
-        return paths
+                path = self._impl_path + '.' + name
+            self.cfgimpl_get_values().setitem(child, value, path,
+                                              force_permissive=force_permissive)
+        else:
+            context = self.cfgimpl_get_context()
+            path = context.cfgimpl_get_description().impl_get_path_by_opt(
+                child._opt)
+            context._setattr(path, value, force_permissive=force_permissive)
 
-    def getpath(self):
-        descr = self.cfgimpl_get_description()
-        context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
-        return context_descr.get_path_by_opt(descr)
+    def __delattr__(self, name):
+        child = getattr(self.cfgimpl_get_description(), name)
+        self.cfgimpl_get_values().__delitem__(child)
+
+    def __getattr__(self, name):
+        return self._getattr(name)
+
+    def _getattr(self, name, force_permissive=False, force_properties=None,
+                 validate=True):
+        """
+        attribute notation mechanism for accessing the value of an option
+        :param name: attribute name
+        :return: option's value if name is an option name, OptionDescription
+                 otherwise
+        """
+        # attribute access by passing a path,
+        # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
+        if '.' in name:
+            homeconfig, name = self.cfgimpl_get_home_by_path(
+                name, force_permissive=force_permissive,
+                force_properties=force_properties)
+            return homeconfig._getattr(name, force_permissive=force_permissive,
+                                       force_properties=force_properties,
+                                       validate=validate)
+        # special attributes
+        if name.startswith('_impl_') or name.startswith('cfgimpl_') \
+                or name.startswith('impl_'):
+            # if it were in __dict__ it would have been found already
+            return object.__getattribute__(self, name)
+        opt_or_descr = getattr(self.cfgimpl_get_description(), name)
+        # symlink options
+        if self._impl_path is None:
+            subpath = name
+        else:
+            subpath = self._impl_path + '.' + name
+        if isinstance(opt_or_descr, SymLinkOption):
+            context = self.cfgimpl_get_context()
+            path = context.cfgimpl_get_description().impl_get_path_by_opt(
+                opt_or_descr._opt)
+            return context._getattr(path, validate=validate,
+                                    force_properties=force_properties,
+                                    force_permissive=force_permissive)
+        elif isinstance(opt_or_descr, OptionDescription):
+            self.cfgimpl_get_settings().validate_properties(
+                opt_or_descr, True, False, path=subpath,
+                force_permissive=force_permissive,
+                force_properties=force_properties)
+            return SubConfig(opt_or_descr, self.cfgimpl_get_context(), subpath)
+        else:
+            return self.cfgimpl_get_values().getitem(
+                opt_or_descr, path=subpath,
+                validate=validate,
+                force_properties=force_properties,
+                force_permissive=force_permissive)
 
     def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
@@ -297,9 +264,11 @@ class SubConfig(object):
         return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
                                                 first=False,
                                                 type_=type_,
-                                                _subpath=self.getpath())
+                                                _subpath=self.cfgimpl_get_path()
+                                                )
 
-    def find_first(self, bytype=None, byname=None, byvalue=None, type_='option'):
+    def find_first(self, bytype=None, byname=None, byvalue=None,
+                   type_='option', display_error=True):
         """
             finds an option recursively in the config
 
@@ -308,137 +277,12 @@ class SubConfig(object):
             :param byvalue: filter by the option's value
             :returns: list of matching Option objects
         """
-        return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
-                                                first=True,
-                                                type_=type_,
-                                                _subpath=self.getpath())
-
-    def make_dict(self, flatten=False, _currpath=None, withoption=None, withvalue=None):
-        """export the whole config into a `dict`
-        :returns: dict of Option's name (or path) and values"""
-        pathsvalues = []
-        if _currpath is None:
-            _currpath = []
-        if withoption is None and withvalue is not None:
-            raise ValueError("make_dict can't filtering with value without option")
-        if withoption is not None:
-            mypath = self.getpath()
-            for path in self.cfgimpl_get_context()._find(bytype=Option,
-                                                         byname=withoption,
-                                                         byvalue=withvalue,
-                                                         first=False,
-                                                         type_='path',
-                                                         _subpath=mypath):
-                path = '.'.join(path.split('.')[:-1])
-                opt = self.cfgimpl_get_context().cfgimpl_get_description().get_opt_by_path(path)
-                if mypath is not None:
-                    if mypath == path:
-                        withoption = None
-                        withvalue = None
-                        break
-                    else:
-                        tmypath = mypath + '.'
-                        if not path.startswith(tmypath):
-                            raise Exception('unexpected path {}, '
-                                            'should start with {}'.format(path, mypath))
-                        path = path[len(tmypath):]
-                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
-        #withoption can be set to None below !
-        if withoption is None:
-            for opt in self.cfgimpl_get_description().getchildren():
-                path = opt._name
-                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
-        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._name)
-                if flatten:
-                    name = opt._name
-                else:
-                    name = '.'.join(_currpath + [opt._name])
-                pathsvalues.append((name, value))
-            except PropertiesOptionError:
-                pass  # this just a hidden or disabled option
-
-
-# ____________________________________________________________
-class Config(SubConfig):
-    "main configuration management entry"
-    __slots__ = ('_cfgimpl_settings', '_cfgimpl_values')
-
-    def __init__(self, descr):
-        """ Configuration option management master class
-
-        :param descr: describes the configuration schema
-        :type descr: an instance of ``option.OptionDescription``
-        :param context: the current root config
-        :type context: `Config`
-        """
-        self._cfgimpl_settings = Setting(self)
-        self._cfgimpl_values = Values(self)
-        super(Config, self).__init__(descr, self)  # , slots)
-        self._cfgimpl_build_all_paths()
-
-    def _cfgimpl_build_all_paths(self):
-        self._cfgimpl_descr.build_cache()
-
-    def unwrap_from_path(self, path):
-        """convenience method to extract and Option() object from the Config()
-        and it is **fast**: finds the option directly in the appropriate
-        namespace
-
-        :returns: Option()
-        """
-        if '.' in path:
-            homeconfig, path = self.cfgimpl_get_home_by_path(path)
-            return getattr(homeconfig._cfgimpl_descr, path)
-        return getattr(self._cfgimpl_descr, path)
-
-    def set(self, **kwargs):
-        """
-        do what I mean"-interface to option setting. Searches all paths
-        starting from that config for matches of the optional arguments
-        and sets the found option if the match is not ambiguous.
-
-        :param kwargs: dict of name strings to values.
-        """
-        #opts, paths = self.cfgimpl_get_description()._cache_paths
-        all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
-        for key, value in kwargs.iteritems():
-            key_p = key.split('.')
-            candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
-            if len(candidates) == 1:
-                name = '.'.join(candidates[0])
-                homeconfig, name = self.cfgimpl_get_home_by_path(name)
-                try:
-                    getattr(homeconfig, name)
-                except MandatoryError:
-                    pass
-                except Exception, e:
-                    raise e  # HiddenOptionError or DisabledOptionError
-                child = getattr(homeconfig._cfgimpl_descr, name)
-                homeconfig.setoption(name, child, value)
-            elif len(candidates) > 1:
-                raise AmbigousOptionError(
-                    'more than one option that ends with %s' % (key, ))
-            else:
-                raise NoMatchingOptionFound(
-                    'there is no option that matches %s'
-                    ' or the option is hidden or disabled' % (key, ))
-
-    def getpath(self):
-        return None
+        return self.cfgimpl_get_context()._find(
+            bytype, byname, byvalue, first=True, type_=type_,
+            _subpath=self.cfgimpl_get_path(), display_error=display_error)
 
     def _find(self, bytype, byname, byvalue, first, type_='option',
-              _subpath=None):
+              _subpath=None, check_properties=True, display_error=True):
         """
         convenience method for finding an option that lives only in the subtree
 
@@ -446,12 +290,13 @@ class Config(SubConfig):
         :return: find list or an exception if nothing has been found
         """
         def _filter_by_name():
-            if byname is None:
-                return True
-            if path == byname or path.endswith('.' + byname):
-                return True
-            else:
-                return False
+            try:
+                if byname is None or path == byname or \
+                        path.endswith('.' + byname):
+                    return True
+            except IndexError:
+                pass
+            return False
 
         def _filter_by_value():
             if byvalue is None:
@@ -460,7 +305,8 @@ class Config(SubConfig):
                 value = getattr(self, path)
                 if value == byvalue:
                     return True
-            except:  # a property restricts the access of the value
+            except PropertiesOptionError:  # a property is a restriction
+                                           # upon the access of the value
                 pass
             return False
 
@@ -482,8 +328,9 @@ class Config(SubConfig):
         #            else:
         #                return False
         #    return False
-        if type_ not in ('option', 'path', 'value'):
-            raise ValueError('unknown type_ type {} for _find'.format(type_))
+        if type_ not in ('option', 'path', 'context', 'value'):
+            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)):
@@ -498,10 +345,12 @@ class Config(SubConfig):
             if not _filter_by_value():
                 continue
             #remove option with propertyerror, ...
-            try:
-                value = getattr(self, path)
-            except:  # a property restricts the access of the value
-                continue
+            if check_properties:
+                try:
+                    value = getattr(self, path)
+                except PropertiesOptionError:
+                    # a property restricts the access of the value
+                    continue
             if not _filter_by_type():
                 continue
             #if not _filter_by_attrs():
@@ -510,28 +359,319 @@ class Config(SubConfig):
                 retval = value
             elif type_ == 'path':
                 retval = path
-            else:
+            elif type_ == 'option':
                 retval = option
+            elif type_ == 'context':
+                retval = self.cfgimpl_get_context()
             if first:
                 return retval
             else:
                 find_results.append(retval)
         if find_results == []:
-            raise NotFoundError("no option found in config with these criteria")
+            if display_error:
+                raise AttributeError(_("no option found in config"
+                                       " with these criteria"))
+            else:
+                #translation is slow
+                raise AttributeError()
         else:
             return find_results
 
+    def make_dict(self, flatten=False, _currpath=None, withoption=None,
+                  withvalue=None):
+        """exports the whole config into a `dict`, for example:
+
+        >>> print cfg.make_dict()
+        {'od2.var4': None, 'od2.var5': None, 'od2.var6': None}
+
+
+
+        :param flatten: returns a dict(name=value) instead of a dict(path=value)
+                        ::
+
+                            >>> print cfg.make_dict(flatten=True)
+                            {'var5': None, 'var4': None, 'var6': None}
+
+        :param withoption: returns the options that are present in the very same
+                           `OptionDescription` than the `withoption` itself::
+
+                                >>> print cfg.make_dict(withoption='var1')
+                                {'od2.var4': None, 'od2.var5': None,
+                                'od2.var6': None,
+                                'od2.var1': u'value',
+                                'od1.var1': None,
+                                'od1.var3': None,
+                                'od1.var2': None}
+
+        :param withvalue: returns the options that have the value `withvalue`
+                          ::
+
+                            >>> print c.make_dict(withoption='var1',
+                                                  withvalue=u'value')
+                            {'od2.var4': None,
+                            'od2.var5': None,
+                            'od2.var6': None,
+                            'od2.var1': u'value'}
+
+        :returns: dict of Option's name (or path) and values
+        """
+        pathsvalues = []
+        if _currpath is None:
+            _currpath = []
+        if withoption is None and withvalue is not None:
+            raise ValueError(_("make_dict can't filtering with value without "
+                               "option"))
+        if withoption is not None:
+            mypath = self.cfgimpl_get_path()
+            for path in self.cfgimpl_get_context()._find(bytype=Option,
+                                                         byname=withoption,
+                                                         byvalue=withvalue,
+                                                         first=False,
+                                                         type_='path',
+                                                         _subpath=mypath):
+                path = '.'.join(path.split('.')[:-1])
+                opt = self.cfgimpl_get_context().cfgimpl_get_description(
+                ).impl_get_opt_by_path(path)
+                if mypath is not None:
+                    if mypath == path:
+                        withoption = None
+                        withvalue = None
+                        break
+                    else:
+                        tmypath = mypath + '.'
+                        if not path.startswith(tmypath):
+                            raise AttributeError(_('unexpected path {0}, '
+                                                   'should start with {1}'
+                                                   '').format(path, mypath))
+                        path = path[len(tmypath):]
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
+        #withoption can be set to None below !
+        if withoption is None:
+            for opt in self.cfgimpl_get_description().impl_getchildren():
+                path = opt._name
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
+        if _currpath == []:
+            options = dict(pathsvalues)
+            return options
+        return pathsvalues
+
+    def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten):
+        if isinstance(opt, OptionDescription):
+            try:
+                pathsvalues += getattr(self, path).make_dict(flatten,
+                                                             _currpath +
+                                                             path.split('.'))
+            except PropertiesOptionError:
+                pass  # this just a hidden or disabled option
+        else:
+            try:
+                value = self._getattr(opt._name)
+                if flatten:
+                    name = opt._name
+                else:
+                    name = '.'.join(_currpath + [opt._name])
+                pathsvalues.append((name, value))
+            except PropertiesOptionError:
+                pass  # this just a hidden or disabled option
+
+    def cfgimpl_get_path(self):
+        descr = self.cfgimpl_get_description()
+        context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
+        return context_descr.impl_get_path_by_opt(descr)
+
+
+class CommonConfig(SubConfig):
+    "abstract base class for the Config and the MetaConfig"
+    __slots__ = ('_impl_values', '_impl_settings', '_impl_meta')
+
+    def _init_storage(self, config_id, is_persistent):
+        if config_id is None:
+            config_id = gen_id(self)
+        import_lib = 'tiramisu.storage.{0}.storage'.format(storage_type)
+        return __import__(import_lib, globals(), locals(), ['Storage'],
+                          -1).Storage(config_id, is_persistent)
+
+    def _impl_build_all_paths(self):
+        self.cfgimpl_get_description().impl_build_cache()
+
+    def read_only(self):
+        "read only is a global config's setting, see `settings.py`"
+        self.cfgimpl_get_settings().read_only()
+
+    def read_write(self):
+        "read write is a global config's setting, see `settings.py`"
+        self.cfgimpl_get_settings().read_write()
+
+    def getowner(self, path):
+        """convenience method to retrieve an option's owner
+        from the config itself
+        """
+        opt = self.cfgimpl_get_description().impl_get_opt_by_path(path)
+        return self.cfgimpl_get_values().getowner(opt)
+
+    def unwrap_from_path(self, path, force_permissive=False):
+        """convenience method to extract and Option() object from the Config()
+        and it is **fast**: finds the option directly in the appropriate
+        namespace
+
+        :returns: Option()
+        """
+        if '.' in path:
+            homeconfig, path = self.cfgimpl_get_home_by_path(
+                path, force_permissive=force_permissive)
+            return getattr(homeconfig.cfgimpl_get_description(), path)
+        return getattr(self.cfgimpl_get_description(), path)
+
+    def cfgimpl_get_path(self):
+        return None
+
+    def cfgimpl_get_meta(self):
+        return self._impl_meta
+
+
+# ____________________________________________________________
+class Config(CommonConfig):
+    "main configuration management entry"
+    __slots__ = tuple()
+
+    def __init__(self, descr, config_id=None, is_persistent=False):
+        """ Configuration option management master class
+
+        :param descr: describes the configuration schema
+        :type descr: an instance of ``option.OptionDescription``
+        :param context: the current root config
+        :type context: `Config`
+        """
+        storage = self._init_storage(config_id, is_persistent)
+        self._impl_settings = Settings(self, storage)
+        self._impl_values = Values(self, storage)
+        super(Config, self).__init__(descr, self)
+        self._impl_build_all_paths()
+        self._impl_meta = None
+        self._impl_informations = {}
+
+    def cfgimpl_reset_cache(self,
+                            only_expired=False,
+                            only=('values', 'settings')):
+        if 'values' in only:
+            self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
+        if 'settings' in only:
+            self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
+
+
+class MetaConfig(CommonConfig):
+    __slots__ = ('_impl_children',)
+
+    def __init__(self, children, meta=True, config_id=None, is_persistent=False):
+        if not isinstance(children, list):
+            raise ValueError(_("metaconfig's children must be a list"))
+        self._impl_descr = None
+        self._impl_path = None
+        if meta:
+            for child in children:
+                if not isinstance(child, CommonConfig):
+                    raise ValueError(_("metaconfig's children "
+                                       "must be config, not {0}"
+                                       ).format(type(child)))
+                if self._impl_descr is None:
+                    self._impl_descr = child.cfgimpl_get_description()
+                elif not self._impl_descr is child.cfgimpl_get_description():
+                    raise ValueError(_('all config in metaconfig must '
+                                       'have the same optiondescription'))
+                if child.cfgimpl_get_meta() is not None:
+                    raise ValueError(_("child has already a metaconfig's"))
+                child._impl_meta = self
+
+        if config_id is None:
+            config_id = gen_id(self)
+        self._impl_children = children
+        storage = self._init_storage(config_id, is_persistent)
+        self._impl_settings = Settings(self, storage)
+        self._impl_values = Values(self, storage)
+        self._impl_meta = None
+        self._impl_informations = {}
+
+    def cfgimpl_get_children(self):
+        return self._impl_children
+
+    def cfgimpl_get_context(self):
+        "a meta config is a config wich has a setting, that is itself"
+        return self
+
+    def cfgimpl_reset_cache(self,
+                            only_expired=False,
+                            only=('values', 'settings')):
+        if 'values' in only:
+            self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
+        if 'settings' in only:
+            self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
+        for child in self._impl_children:
+            child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
+
+    def set_contexts(self, path, value):
+        for child in self._impl_children:
+            try:
+                if not isinstance(child, MetaConfig):
+                    setattr(child, path, value)
+                else:
+                    child.set_contexts(path, value)
+            except PropertiesOptionError:
+                pass
+
+    def find_first_contexts(self, byname=None, bypath=None, byvalue=None,
+                            type_='context', display_error=True):
+        ret = []
+        try:
+            if bypath is None and byname is not None and \
+                    self.cfgimpl_get_description() is not None:
+                bypath = self._find(bytype=None, byvalue=None, byname=byname,
+                                    first=True, type_='path',
+                                    check_properties=False)
+        except ConfigError:
+            pass
+        for child in self._impl_children:
+            try:
+                if not isinstance(child, MetaConfig):
+                    if bypath is not None:
+                        if byvalue is not None:
+                            if getattr(child, bypath) == byvalue:
+                                ret.append(child)
+                        else:
+                            #not raise
+                            getattr(child, bypath)
+                            ret.append(child)
+                    else:
+                        ret.append(child.find_first(byname=byname,
+                                                    byvalue=byvalue,
+                                                    type_=type_,
+                                                    display_error=False))
+                else:
+                    ret.extend(child.find_first_contexts(byname=byname,
+                                                         bypath=bypath,
+                                                         byvalue=byvalue,
+                                                         type_=type_,
+                                                         display_error=False))
+            except AttributeError:
+                pass
+        return ret
+
 
 def mandatory_warnings(config):
     """convenience function to trace Options that are mandatory and
     where no value has been set
 
     :returns: generator of mandatory Option's path
+
     """
-    for path in config.cfgimpl_get_description().getpaths(include_groups=True):
+    #if value in cache, properties are not calculated
+    config.cfgimpl_reset_cache(only=('values',))
+    for path in config.cfgimpl_get_description().impl_getpaths(
+            include_groups=True):
         try:
-            config._getattr(path, force_properties=('mandatory',))
-        except MandatoryError:
-            yield path
-        except PropertiesOptionError:
-            pass
+            config._getattr(path, force_properties=frozenset(('mandatory',)))
+            # XXX raise Exception("ca passe ici")
+            # XXX depuis l'exterieur on donne un paht maintenant ! perturbant !
+        except PropertiesOptionError, err:
+            if err.proptype == ['mandatory']:
+                yield path
+    config.cfgimpl_reset_cache(only=('values',))