add option name's validation and rename Option method with objimpl_
[tiramisu.git] / tiramisu / config.py
index d268787..a89f8e6 100644 (file)
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 "pretty small and local configuration management tool"
-# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors)
+# 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 General Public License as published by
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
-# The original `Config` design model is unproudly borrowed from 
+# 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.error import (PropertiesOptionError, ConfigError, NotFoundError, 
-    AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, 
-    MandatoryError, MethodCallError)
-from tiramisu.option import (OptionDescription, Option, SymLinkOption, 
-    group_types, Multi, apply_requires)
-from tiramisu.autolib import carry_out_calculation
-
-# ______________________________________________________________________
-# generic owner. 'default' is the general config owner after init time
-default_owner = 'user'
-# ____________________________________________________________
-class Config(object):
-    _cfgimpl_properties = ['hidden', 'disabled']
-    _cfgimpl_mandatory = True
-    _cfgimpl_frozen = True
-    _cfgimpl_owner = default_owner
-    _cfgimpl_toplevel = None
-    
-    def __init__(self, descr, parent=None, **overrides):
+#from inspect import getmembers, ismethod
+from tiramisu.error import PropertiesOptionError
+from tiramisu.option import OptionDescription, Option, SymLinkOption
+from tiramisu.setting import groups, Setting
+from tiramisu.value import Values
+from tiramisu.i18n import _
+
+
+class SubConfig(object):
+    "sub configuration management entry"
+    __slots__ = ('_cfgimpl_descr', '_cfgimpl_context')
+
+    def __init__(self, descr, context):
+        """ 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`
+        """
+        # main option description
+        if not isinstance(descr, OptionDescription):
+            raise ValueError(_('descr must be an optiondescription, not {0}').format(type(descr)))
         self._cfgimpl_descr = descr
-        self._cfgimpl_value_owners = {}
-        self._cfgimpl_parent = parent
-        # `Config()` indeed takes care of the `Option()`'s values
-        self._cfgimpl_values = {}
-        self._cfgimpl_previous_values = {}
-        # XXX warnings are a great idea, let's make up a better use of it 
-        self._cfgimpl_warnings = []
-        self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
-        # `freeze()` allows us to carry out this calculation again if necessary 
-        self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen 
-        self._cfgimpl_build(overrides)
-
-    def _validate_duplicates(self, children):
-        duplicates = []
-        for dup in children:
-            if dup._name not in duplicates:
-                duplicates.append(dup._name)
-            else:
-                raise ConflictConfigError('duplicate option name: ' 
-                    '{0}'.format(dup._name))
-        
-    def _cfgimpl_build(self, overrides):
-        self._validate_duplicates(self._cfgimpl_descr._children)
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
-                if child.is_multi():
-                    childdef = Multi(copy(child.getdefault()), config=self, 
-                                     child=child)
-                    self._cfgimpl_values[child._name] = childdef
-                    self._cfgimpl_previous_values[child._name] = list(childdef)
-                    self._cfgimpl_value_owners[child._name] = ['default' \
-                        for i in range(len(child.getdefault() ))]
-                else:
-                    childdef = child.getdefault()
-                    self._cfgimpl_values[child._name] = childdef
-                    self._cfgimpl_previous_values[child._name] = childdef 
-                    self._cfgimpl_value_owners[child._name] = 'default'
-            elif isinstance(child, OptionDescription):
-                self._validate_duplicates(child._children)
-                self._cfgimpl_values[child._name] = Config(child, parent=self)
-        self.override(overrides)
-
-    def cfgimpl_update(self):
-        "dynamically adds `Option()` or `OptionDescription()`"
-        # Nothing is static. Everything evolve.
-        # FIXME this is an update for new options in the schema only 
-        # see the update_child() method of the descr object 
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
-                if child._name not in self._cfgimpl_values:
-                    if child.is_multi():
-                        self._cfgimpl_values[child._name] = Multi(
-                                copy(child.getdefault()), config=self, child=child)
-                        self._cfgimpl_value_owners[child._name] = ['default' \
-                                for i in range(len(child.getdefault() ))]
-                    else:
-                        self._cfgimpl_values[child._name] = copy(child.getdefault())
-                        self._cfgimpl_value_owners[child._name] = 'default'
-            elif isinstance(child, OptionDescription):
-                if child._name not in self._cfgimpl_values:
-                    self._cfgimpl_values[child._name] = Config(child, parent=self)
-
-    def override(self, overrides):
-        for name, value in overrides.iteritems():
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            homeconfig.setoption(name, value, 'default')
-
-    def cfgimpl_set_owner(self, owner):
-        self._cfgimpl_owner = owner
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, OptionDescription):
-                self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
-    # ____________________________________________________________
-    def _cfgimpl_has_properties(self):
-        return bool(len(self._cfgimpl_properties))
-
-    def _cfgimpl_has_property(self, propname):
-        return propname in self._cfgimpl_properties
-
-    def cfgimpl_enable_property(self, propname):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Config() object") 
-        if propname not in self._cfgimpl_properties:
-            self._cfgimpl_properties.append(propname)
-    
-    def cfgimpl_disable_property(self, propname):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Config() object") 
-        if self._cfgimpl_has_property(propname):
-            self._cfgimpl_properties.remove(propname)
-
-    def cfgimpl_non_mandatory(self):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_mandatory machin() shall not be"
-                                           "used with non-root Confit() object") 
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_mandatory = False
+        # 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
+            #self.__dict__[name] = value
+            object.__setattr__(self, name, value)
             return
+        self._setattr(name, value)
+
+    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+        self.cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
+
+    def _setattr(self, name, value, force_permissive=False):
         if '.' in name:
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            return setattr(homeconfig, name, value)
-        if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
-            self._validate(name, getattr(self._cfgimpl_descr, name))
-        self.setoption(name, value, self._cfgimpl_owner)
-        
-    def _validate(self, name, opt_or_descr):
-        apply_requires(opt_or_descr, self) 
-        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 = opt_or_descr.properties
-        for proper in properties:
-            if not self._cfgimpl_toplevel._cfgimpl_has_property(proper):
-                properties.remove(proper)
-        if properties != []:
-            raise PropertiesOptionError("trying to access"
-                    " to an option named: {0} with properties"
-                    " {1}".format(name, str(properties)), 
-                    properties)
-
-    def _is_empty(self, opt):
-        if (not opt.is_multi() and self._cfgimpl_values[opt._name] == None) or \
-            (opt.is_multi() and (self._cfgimpl_values[opt._name] == [] or \
-                None in self._cfgimpl_values[opt._name])):
-            return True
-        return False
+            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.cfgimpl_get_values().setitem(child, value,
+                                              force_permissive=force_permissive)
+        else:
+            child._setoption(self.cfgimpl_get_context(), value)
+
+    def __delattr__(self, name):
+        child = getattr(self._cfgimpl_descr, name)
+        del(self.cfgimpl_get_values()[child])
 
     def __getattr__(self, name):
-        # attribute access by passing a path, 
-        # for instance getattr(self, "creole.general.family.adresse_ip_eth0") 
+        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)
-            return getattr(homeconfig, name)
-        opt_or_descr = getattr(self._cfgimpl_descr, name)
-        # symlink options 
-        if type(opt_or_descr) == SymLinkOption:
-            return getattr(self, opt_or_descr.path)
-        if name not in self._cfgimpl_values:
-            raise AttributeError("%s object has no attribute %s" %
-                                 (self.__class__, name))
-        self._validate(name, opt_or_descr)
+            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('_cfgimpl_'):
+        if name.startswith('_cfgimpl_') or name.startswith('cfgimpl_'):
             # if it were in __dict__ it would have been found already
-            return self.__dict__[name]
-            raise AttributeError("%s object has no attribute %s" %
-                                 (self.__class__, name))
-        if not isinstance(opt_or_descr, OptionDescription):
-            # options with callbacks (fill or auto) 
-            if opt_or_descr.has_callback():
-                value = self._cfgimpl_values[name]
-                if (not opt_or_descr.is_frozen() or \
-                        not opt_or_descr.is_forced_on_freeze()) and value != None:
-                    if opt_or_descr.is_multi():
-                        if None not in value:
-                            return value
-                    else:
-                        return value
-                result = carry_out_calculation(name, 
-                            callback=opt_or_descr.getcallback(),
-                            callback_params=opt_or_descr.getcallback_params(),
-                            config=self._cfgimpl_get_toplevel())
-                # this result **shall not** be a list 
-                # for example, [1, 2, 3, None] -> [1, 2, 3, result]
-                if isinstance(result, list):
-                    raise ConfigError('invalid calculated value returned'
-                        ' for option {0} : shall not be a list'.format(name))
-                if result != None and not opt_or_descr._validate(result):
-                    raise ConfigError('invalid calculated value returned'
-                        ' for option {0}'.format(name))
-                if opt_or_descr.is_multi():
-                    if value == []:
-                        _result = Multi([result], value.config, value.child)
-                    else:
-                        _result = Multi([], value.config, value.child)
-                        for val in value:
-                            if val == None:
-                                val = result
-                            _result.append(val)
-                else:
-                    _result = result
-                return _result
-
-            # mandatory options
-            homeconfig = self._cfgimpl_get_toplevel()
-            mandatory = homeconfig._cfgimpl_mandatory
-            if opt_or_descr.is_mandatory() and mandatory:
-                if self._is_empty(opt_or_descr) and \
-                        opt_or_descr.is_empty_by_default():
-                    raise MandatoryError("option: {0} is mandatory " 
-                                          "and shall have a value".format(name))
-            # frozen and force default
-            if opt_or_descr.is_forced_on_freeze():
-                return opt_or_descr.getdefault()
-        
-        return self._cfgimpl_values[name]
-                
-    def __dir__(self):
-        #from_type = dir(type(self))
-        from_dict = list(self.__dict__)
-        extras = list(self._cfgimpl_values)
-        return sorted(set(extras + from_dict))
-
-    def unwrap_from_name(self, name):
-        # didn't have to stoop so low: `self.get()` must be the proper method
-        # **and it is slow**: it recursively searches into the namespaces
-        paths = self.getpaths(allpaths=True)
-        opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
-        all_paths = [p.split(".") for p in self.getpaths()]
-        for pth in all_paths:
-            if name in pth:
-                return opts[".".join(pth)]
-        raise NotFoundError("name: {0} not found".format(name))
-        
-    def unwrap_from_path(self, path):
-        # didn't have to stoop so low, `geattr(self, path)` is much better
-        # **fast**: finds the option directly in the appropriate namespace
-        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 __delattr__(self, name):
-        # if you use delattr you are responsible for all bad things happening
-        if name.startswith('_cfgimpl_'):
-            del self.__dict__[name]
-            return
-        self._cfgimpl_value_owners[name] = 'default'
-        opt = getattr(self._cfgimpl_descr, name)
-        if isinstance(opt, OptionDescription):
-            raise AttributeError("can't option subgroup")
-        self._cfgimpl_values[name] = getattr(opt, 'default', None)
-
-    def setoption(self, name, value, who=None):
-        #who is **not necessarily** a owner, because it cannot be a list
-        #FIXME : sortir le setoption pour les multi, ca ne devrait pas être la
-        child = getattr(self._cfgimpl_descr, name)
-        if who == None:
-            if child.is_multi():
-                newowner = [self._cfgimpl_owner for i in range(len(value))] 
-            else:
-                newowner = self._cfgimpl_owner
+            object.__getattr__(self, name)
+        opt_or_descr = getattr(self.cfgimpl_get_description(), name)
+        # symlink options
+        if isinstance(opt_or_descr, SymLinkOption):
+            rootconfig = self.cfgimpl_get_context()
+            path = rootconfig.cfgimpl_get_description().objimpl_get_path_by_opt(opt_or_descr.opt)
+            return rootconfig._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,
+                                                            force_permissive=force_permissive,
+                                                            force_properties=force_properties)
+            return SubConfig(opt_or_descr, self._cfgimpl_context)
         else:
-            if type(child) != SymLinkOption:
-                if child.is_multi():
-                    if type(value) != Multi:
-                        if type(value) == list:
-                            value = Multi(value, self, child)
-                        else:
-                            raise ConfigError("invalid value for option:"
-                                       " {0} that is set to multi".format(name))
-                    newowner = [who for i in range(len(value))]
-                else:
-                    newowner = who 
-        if type(child) != SymLinkOption:
-            if child.has_callback() and who=='default':
-                raise TypeError("trying to set a value to an option "
-                    "wich has a callback: {0}".format(name))
-            child.setoption(self, value, who)
-            if (value is None and who != 'default' and not child.is_multi()):
-                child.setowner(self, 'default')
-                self._cfgimpl_values[name] = copy(child.getdefault())
-            elif (value == [] and who != 'default' and child.is_multi()):
-                child.setowner(self, ['default' for i in range(len(child.getdefault()))])
-                self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
-                            config=self, child=child)
-            else:         
-                child.setowner(self, newowner)
-        else:
-            homeconfig = self._cfgimpl_get_toplevel()
-            child.setoption(homeconfig, value, who)
-
-    def set(self, **kwargs):
-        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
-                homeconfig.setoption(name, value, self._cfgimpl_owner)
-            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, ))
+            return self.cfgimpl_get_values().getitem(opt_or_descr,
+                                                     validate=validate,
+                                                     force_properties=force_properties,
+                                                     force_permissive=force_permissive)
 
-    def get(self, name):
-        paths = self.getpaths(allpaths=True)
-        pathsvalues = []
-        for path in paths:
-            pathname = path.split('.')[-1]
-            if pathname == name:
-                try:
-                    value = getattr(self, path)            
-                    return value 
-                except Exception, e:
-                    raise e
-        raise NotFoundError("option {0} not found in config".format(name))                    
-
-    def _cfgimpl_get_home_by_path(self, path):
-        """returns tuple (config, name)"""
+    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]:
-            self = getattr(self, step)
+            self = self._getattr(step,
+                                 force_permissive=force_permissive,
+                                 force_properties=force_properties)
         return self, path[-1]
 
-    def _cfgimpl_get_toplevel(self):
-        while self._cfgimpl_parent is not None:
-            self = self._cfgimpl_parent
-        return self
-    
-    def _cfgimpl_get_path(self):
-        subpath = []
-        obj = self
-        while obj._cfgimpl_parent is not None:
-            subpath.insert(0, obj._cfgimpl_descr._name)
-            obj = obj._cfgimpl_parent
-        return ".".join(subpath)
-
-
-    def cfgimpl_previous_value(self, path):
-        home, name = self._cfgimpl_get_home_by_path(path)
-        return home._cfgimpl_previous_values[name]
-    
-    def get_previous_value(self, name):
-        return self._cfgimpl_previous_values[name]
-             
-    def add_warning(self, warning):
-        self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
-
-    def get_warnings(self):
-        return self._cfgimpl_get_toplevel()._cfgimpl_warnings
-    # ____________________________________________________________
-    # freeze and read-write statuses
-    def cfgimpl_freeze(self):
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_frozen = True
-        self._cfgimpl_frozen = True
-
-    def cfgimpl_unfreeze(self):
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_frozen = False
-        self._cfgimpl_frozen = False
-
-    def is_frozen(self):
-        # it should be the same value as self._cfgimpl_frozen...
-        rootconfig = self._cfgimpl_get_toplevel()
-        return rootconfig._cfgimpl_frozen
-
-    def is_mandatory(self):
-        rootconfig = self._cfgimpl_get_toplevel()
-        return rootconfig._cfgimpl_mandatory
-
-    def cfgimpl_read_only(self):
-        # hung up on freeze, hidden and disabled concepts 
-        self.cfgimpl_freeze()
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig.cfgimpl_disable_property('hidden')
-        rootconfig.cfgimpl_enable_property('disabled')
-        rootconfig._cfgimpl_mandatory = True
-
-    def cfgimpl_read_write(self):
-        # hung up on freeze, hidden and disabled concepts
-        self.cfgimpl_unfreeze()
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig.cfgimpl_enable_property('hidden')
-        rootconfig.cfgimpl_disable_property('disabled')
-        rootconfig._cfgimpl_mandatory = False
-    # ____________________________________________________________
-    def getkey(self):
-        return self._cfgimpl_descr.getkey(self)
-
     def __hash__(self):
-        return hash(self.getkey())
+        return hash(self.cfgimpl_get_description().objimpl_getkey(self))
 
     def __eq__(self, other):
-        return self.getkey() == other.getkey()
+        "Config comparison"
+        if not isinstance(other, Config):
+            return False
+        return self.cfgimpl_get_description().objimpl_getkey(self) == \
+            other.cfgimpl_get_description().objimpl_getkey(other)
 
     def __ne__(self, other):
+        "Config comparison"
+        if not isinstance(other, Config):
+            return False
         return not self == other
 
+    # ______________________________________________________________________
     def __iter__(self):
-        # iteration only on Options (not OptionDescriptions)
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
+        """Pythonesque way of parsing group's ordered options.
+        iteration only on Options (not OptionDescriptions)"""
+        for child in self.cfgimpl_get_description().objimpl_getchildren():
+            if not isinstance(child, OptionDescription):
                 try:
                     yield child._name, getattr(self, child._name)
-                except:
-                    pass # option with properties
+                except GeneratorExit:
+                    raise StopIteration
+                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_get_description().objimpl_getchildren():
+            try:
+                yield child._name, getattr(self, child._name)
+            except GeneratorExit:
+                raise StopIteration
+            except PropertiesOptionError:
+                pass  # option with properties
 
     def iter_groups(self, group_type=None):
-        "iteration on OptionDescriptions"
-        if group_type == None:
-            groups = group_types
-        else:
-            if group_type not in group_types:
-                raise TypeError("Unknown group_type: {0}".format(group_type))
-            groups = [group_type]
-        for child in self._cfgimpl_descr._children:
+        """iteration on groups objects only.
+        All groups are returned if `group_type` is `None`, otherwise the groups
+        can be filtered by categories (families, or whatever).
+
+        :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_get_description().objimpl_getchildren():
             if isinstance(child, OptionDescription):
-                    try:
-                        if child.get_group_type() in groups: 
-                            yield child._name, getattr(self, child._name)
-                    except:
-                        pass # hidden, disabled option
-                    
-    def __str__(self, indent=""):
+                try:
+                    if group_type is None or (group_type is not None and
+                                              child.objimpl_get_group_type() == group_type):
+                        yield child._name, getattr(self, child._name)
+                except GeneratorExit:
+                    raise StopIteration
+                except PropertiesOptionError:
+                    pass
+    # ______________________________________________________________________
+
+    def __str__(self):
+        "Config's string representation"
         lines = []
-        children = [(child._name, child)
-                    for child in self._cfgimpl_descr._children]
-        children.sort()
-        for name, child in children:
-            if self._cfgimpl_value_owners.get(name, None) == 'default':
-                continue
-            value = getattr(self, name)
-            if isinstance(value, Config):
-                substr = value.__str__(indent + "    ")
-            else:
-                substr = "%s    %s = %s" % (indent, name, value)
-            if substr:
-                lines.append(substr)
-        if indent and not lines:
-            return ''   # hide subgroups with all default values
-        lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
+        for name, grp in self.iter_groups():
+            lines.append("[%s]" % name)
+        for name, value in self:
+            try:
+                lines.append("%s = %s" % (name, value))
+            except PropertiesOptionError:
+                pass
         return '\n'.join(lines)
 
-    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)
+    __repr__ = __str__
+
+    def cfgimpl_get_path(self):
+        descr = self.cfgimpl_get_description()
+        context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
+        return context_descr.objimpl_get_path_by_opt(descr)
+
+    def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
-        paths = []
-        for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
-            try: 
-                value = getattr(self, path)
-                
-            except MandatoryError:
-                if mandatory or allpaths:
-                    paths.append(path)
+            finds a list of options recursively in the config
+
+            :param bytype: Option class (BoolOption, StrOption, ...)
+            :param byname: filter by Option._name
+            :param byvalue: filter by the option's value
+            :returns: list of matching Option objects
+        """
+        return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
+                                                first=False,
+                                                type_=type_,
+                                                _subpath=self.cfgimpl_get_path())
+
+    def find_first(self, bytype=None, byname=None, byvalue=None, type_='option'):
+        """
+            finds an option recursively in the config
+
+            :param bytype: Option class (BoolOption, StrOption, ...)
+            :param byname: filter by Option._name
+            :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.cfgimpl_get_path())
+
+    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.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().objimpl_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().objimpl_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:
-                if allpaths:
-                    paths.append(path) # hidden or disabled or mandatory option added
+                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.objimpl_build_cache()
+
+    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)
+
+    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 cfgimpl_get_path(self):
+        return None
+
+    def _find(self, bytype, byname, byvalue, first, type_='option',
+              _subpath=None):
+        """
+        convenience method for finding an option that lives only in the subtree
+
+        :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:
+                return True
+            if path == byname or path.endswith('.' + byname):
+                return True
             else:
-                 paths.append(path)
-        return paths 
-        
-def make_dict(config, flatten=False):
-    paths = config.getpaths()
-    pathsvalues = []
-    for path in paths:
-        if flatten:
-            pathname = path.split('.')[-1]
+                return False
+
+        def _filter_by_value():
+            if byvalue is None:
+                return True
+            try:
+                value = getattr(self, path)
+                if value == byvalue:
+                    return True
+            except PropertiesOptionError:  # a property restricts the access of the value
+                pass
+            return False
+
+        def _filter_by_type():
+            if bytype is None:
+                return True
+            if isinstance(option, bytype):
+                return True
+            return False
+
+        #def _filter_by_attrs():
+        #    if byattrs is None:
+        #        return True
+        #    for key, val in byattrs.items():
+        #        print "----", path, key
+        #        if path == key or path.endswith('.' + key):
+        #            if value == val:
+        #                return True
+        #            else:
+        #                return False
+        #    return False
+        if type_ not in ('option', 'path', '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)):
+            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 not _filter_by_value():
+                continue
+            #remove option with propertyerror, ...
+            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():
+            #    continue
+            if type_ == 'value':
+                retval = value
+            elif type_ == 'path':
+                retval = path
+            else:
+                retval = option
+            if first:
+                return retval
+            else:
+                find_results.append(retval)
+        if find_results == []:
+            raise AttributeError(_("no option found in config with these criteria"))
         else:
-            pathname = path
-        try:
-            value = getattr(config, path)            
-            pathsvalues.append((pathname, value))      
-        except:
-            pass # this just a hidden or disabled option
-    options = dict(pathsvalues)
-    return options
+            return find_results
+
 
 def mandatory_warnings(config):
-    mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
-    config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
-    for path in config._cfgimpl_descr.getpaths(include_groups=True):
+    """convenience function to trace Options that are mandatory and
+    where no value has been set
+
+    :returns: generator of mandatory Option's path
+    """
+    #if value in cache, properties are not calculated
+    config.cfgimpl_reset_cache(only=('values',))
+    for path in config.cfgimpl_get_description().objimpl_getpaths(include_groups=True):
         try:
-            value = getattr(config, path)
-        except MandatoryError:
-            yield path
-        except PropertiesOptionError:
-            pass
-    config._cfgimpl_get_toplevel()._cfgimpl_mandatory = mandatory
+            config._getattr(path, force_properties=('mandatory',))
+        except PropertiesOptionError, err:
+            if err.proptype == ['mandatory']:
+                yield path
+    config.cfgimpl_reset_cache(only=('values',))