option's name shall not have an api's method name
[tiramisu.git] / tiramisu / config.py
index 14240bf..ab2b04d 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'
+from inspect import getmembers, ismethod
+from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
+    AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
+    MandatoryError, MethodCallError, NoValueReturned)
+from tiramisu.option import (OptionDescription, Option, SymLinkOption,
+    apply_requires)
+from tiramisu.setting import groups, owners, Setting
+from tiramisu.value import Values
+
+
 # ____________________________________________________________
 class Config(object):
-    _cfgimpl_properties = ['hidden', 'disabled']
-    _cfgimpl_mandatory = True
-    _cfgimpl_frozen = False
-    _cfgimpl_owner = default_owner
+    "main configuration management entry"
     _cfgimpl_toplevel = None
-# TODO implement unicity by name
-#    _cfgimpl_unique_names = True
-    
-    def __init__(self, descr, parent=None, **overrides):
+
+    def __init__(self, descr, parent=None, context=None):
+        """ Configuration option management master class
+
+        :param descr: describes the configuration schema
+        :type descr: an instance of ``option.OptionDescription``
+        :param parent: is None if the ``Config`` is root parent Config otherwise
+        :type parent: ``Config``
+        :param context: the current root config
+        :type context: `Config`
+        """
+        # main option description
         self._cfgimpl_descr = descr
-        self._cfgimpl_value_owners = {}
+        # sub option descriptions
+        self._cfgimpl_subconfigs = {}
         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 
+        if context is None:
+            self._cfgimpl_context = self
+        else:
+            self._cfgimpl_context = context
+        if parent is None:
+            self._cfgimpl_settings = Setting()
+            self._cfgimpl_values = Values(self._cfgimpl_context)
+        else:
+            if context is None:
+                raise ConfigError("cannot find a value for this config")
+            self._cfgimpl_settings = None
+            self._cfgimpl_values = None
+        "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)
+        # some api members shall not be used as option's names !
+        methods = getmembers(self, ismethod)
+        self._cfgimpl_slots = [key for key, value in methods \
+                              if not key.startswith("_")]
+        self._cfgimpl_build()
+
+    def cfgimpl_get_settings(self):
+        return self._cfgimpl_context._cfgimpl_settings
+
+    def cfgimpl_set_settings(self, settings):
+        if not isinstance(settings, Setting):
+            raise ConfigError("setting not allowed")
+        self._cfgimpl_context._cfgimpl_settings = settings
 
     def _validate_duplicates(self, children):
+        """duplicates Option names in the schema
+        :type children: list of `Option` or `OptionDescription`
+        """
         duplicates = []
         for dup in children:
             if dup._name not in duplicates:
                 duplicates.append(dup._name)
             else:
-                raise ConflictConfigError('duplicate option name: ' 
+                raise ConflictConfigError('duplicate option name: '
                     '{0}'.format(dup._name))
 
-# TODO implement unicity by name
-#    def _validate_duplicates_for_names(self, children):
-#        "validates duplicates names agains the whole config"
-#        rootconfig = self._cfgimpl_get_toplevel()
-#        if self._cfgimpl_unique_names:
-#            for dup in children:
-#                try:
-#                    print dup._name
-#                    try:
-#                        print rootconfig.get(dup._name)
-#                    except AttributeError:
-#                        pass
-#                    raise NotFoundError
-#                    #rootconfig.get(dup._name)
-#                except NotFoundError:
-#                    pass # no identical names, it's fine
-#                else:
-#                    raise ConflictConfigError('duplicate option name: ' 
-#                        '{0}'.format(dup._name))
-        
-    def _cfgimpl_build(self, overrides):
+    def _cfgimpl_build(self):
+        """
+        - builds the config object from the schema
+        - settles various default values for options
+        """
         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')
+        if self._cfgimpl_descr.group_type == groups.master:
+            mastername = self._cfgimpl_descr._name
+            masteropt = getattr(self._cfgimpl_descr, mastername)
+            self._cfgimpl_context._cfgimpl_values.masters[masteropt] = []
 
-    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 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.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
+                self._validate_duplicates(child._children)
+                self._cfgimpl_subconfigs[child] = Config(child, parent=self,
+                                                context=self._cfgimpl_context)
+            else:
+                if child._name in self._cfgimpl_slots:
+                    raise NameError("invalid name for the option:"
+                                    " {0}".format(child._name))
+
+            if (self._cfgimpl_descr.group_type == groups.master and
+                    child != masteropt):
+                self._cfgimpl_context._cfgimpl_values.slaves[child] = masteropt
+                self._cfgimpl_context._cfgimpl_values.masters[masteropt].append(child)
 
     # ____________________________________________________________
+    # attribute methods
     def __setattr__(self, name, value):
-        if '.' in name:
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            return setattr(homeconfig, name, value)
-
+        "attribute notation mechanism for the setting of the value of an option"
         if name.startswith('_cfgimpl_'):
             self.__dict__[name] = value
             return
-        if self.is_frozen() and getattr(self, name) != value:
-            raise TypeError("trying to change a value in a frozen config"
-                                                ": {0} {1}".format(name, value))
-#        if self.is_mandatory() and value == None:
-#            raise MandatoryError("trying to reset option: {0} wich lives in a"
-#                    " mandatory group: {1}".format(name,
-#                        self._cfgimpl_descr._name))
+        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 type(opt_or_descr) == OptionDescription:
-            if self._cfgimpl_toplevel._cfgimpl_has_properties() and \
-                    opt_or_descr.has_properties():
-                raise PropertiesOptionError("trying to access"
-                        " to an option named: {0} with properties"
-                        " {1}".format(name, opt_or_descr.properties), 
-                        opt_or_descr.properties)
-            if self._cfgimpl_toplevel._cfgimpl_has_properties() and \
-                    self._cfgimpl_descr.has_properties():
-                raise PropertiesOptionError("trying to access"
-                        " to an option's group named: {0}"
-                        " for option named: {1} with properties {2}".format(
-                            self._cfgimpl_descr._name, name,
-                            opt_or_descr.properties), 
-                        self._cfgimpl_descr.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
+        if name in self._cfgimpl_slots:
+            raise NameError("invalid name for the option:"
+                            " {0}".format(name))
+        self.setoption(name, value)
+
+    def _validate(self, name, opt_or_descr, permissive=False):
+        "validation for the setattr and the getattr"
+        apply_requires(opt_or_descr, self, permissive=permissive)
+        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 = copy(opt_or_descr.properties)
+        for proper in copy(properties):
+            if not self._cfgimpl_context._cfgimpl_settings.has_property(proper):
+                properties.remove(proper)
+        if permissive:
+            for perm in self._cfgimpl_context._cfgimpl_settings.permissive:
+                if perm in properties:
+                    properties.remove(perm)
+        if properties != []:
+            raise PropertiesOptionError("trying to access"
+                    " to an option named: {0} with properties"
+                    " {1}".format(name, str(properties)),
+                    properties)
 
     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 fill_multi(self, opt, result, use_default_multi=False, default_multi=None):
+#        """fills a multi option with default and calculated values
+#        """
+#        # FIXME C'EST ENCORE DU N'IMPORTE QUOI
+#        if not isinstance(result, list):
+#            _result = [result]
+#        else:
+#            _result = result
+#        return Multi(_result, self._cfgimpl_context, opt)
+
+    def _getattr(self, name, permissive=False):
+        """
+        attribute notation mechanism for accessing the value of an option
+        :param name: attribute name
+        :param permissive: permissive doesn't raise some property error
+                          (see ``permissive``)
+        :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)
+            return homeconfig._getattr(name, permissive)
         opt_or_descr = getattr(self._cfgimpl_descr, name)
-        # symlink options 
+        # symlink options
         if type(opt_or_descr) == SymLinkOption:
-            return getattr(self, opt_or_descr.path)
-        self._validate(name, opt_or_descr)
+            rootconfig = self._cfgimpl_get_toplevel()
+            return getattr(rootconfig, opt_or_descr.path)
+
+        self._validate(name, opt_or_descr, permissive)
+        if isinstance(opt_or_descr, OptionDescription):
+            if opt_or_descr not in self._cfgimpl_subconfigs:
+                raise AttributeError("%s with name %s object has no attribute %s" %
+                                 (self.__class__, opt_or_descr._name, name))
+            return self._cfgimpl_subconfigs[opt_or_descr]
         # special attributes
         if 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 name not in self._cfgimpl_values:
-            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))
+        return self._cfgimpl_context._cfgimpl_values[opt_or_descr]
 
     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
+        """convenience method to extract and Option() object from the Config()
+        **and it is slow**: it recursively searches into the namespaces
+
+        :returns: Option()
+        """
         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()]
@@ -293,67 +213,34 @@ class Config(object):
             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
+        """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 __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
+        """effectively modifies the value of an Option()
+        (typically called by the __setattr__)
+        """
         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
-        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 oldowner == who:
-#                oldvalue = getattr(self, name)
-#                if oldvalue == value: 
-#                    return
-            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)
+        child.setoption(self, value)
 
     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.
+        """
         all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
         for key, value in kwargs.iteritems():
             key_p = key.split('.')
@@ -366,100 +253,80 @@ class Config(object):
                 except MandatoryError:
                     pass
                 except Exception, e:
-                    raise e # HiddenOptionError or DisabledOptionError
-                homeconfig.setoption(name, value, self._cfgimpl_owner)
+                    raise e  # HiddenOptionError or DisabledOptionError
+                homeconfig.setoption(name, 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, ))
+                    'there is no option that matches %s'
+                    ' or the option is hidden or disabled' % (key, ))
 
     def get(self, name):
+        """
+        same as a `find_first()` method in a config that has identical names:
+        it returns the first item of an option named `name`
+
+        much like the attribute access way, except that
+        the search for the option is performed recursively in the whole
+        configuration tree.
+        **carefull**: very slow !
+
+        :returns: option value.
+        """
         paths = self.getpaths(allpaths=True)
         pathsvalues = []
         for path in paths:
             pathname = path.split('.')[-1]
             if pathname == name:
                 try:
-                    value = getattr(self, path)            
-                    return value 
+                    value = getattr(self, path)
+                    return value
                 except Exception, e:
                     raise e
-        raise NotFoundError("option {0} not found in config".format(name))                    
+        raise NotFoundError("option {0} not found in config".format(name))
 
     def _cfgimpl_get_home_by_path(self, path):
-        """returns tuple (config, name)"""
+        """:returns: tuple (config, name)"""
         path = path.split('.')
-        
         for step in path[:-1]:
             self = getattr(self, step)
         return self, path[-1]
 
     def _cfgimpl_get_toplevel(self):
+        ":returns: root config"
         while self._cfgimpl_parent is not None:
             self = self._cfgimpl_parent
         return self
-    
+
     def _cfgimpl_get_path(self):
+        "the path in the attribute access meaning."
         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 cfgimpl_previous_value(self, path):
+#        "stores the previous value"
+#        home, name = self._cfgimpl_get_home_by_path(path)
+#        # FIXME  fucking name
+#        return home._cfgimpl_context._cfgimpl_values.previous_values[name]
+
+#    def get_previous_value(self, name):
+#        "for the time being, only the previous Option's value is accessible"
+#        return self._cfgimpl_context._cfgimpl_values.previous_values[name]
+    # ______________________________________________________________________
     def add_warning(self, warning):
+        "Config implements its own warning pile. Could be useful"
         self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
 
     def get_warnings(self):
+        "Config implements its own warning pile"
         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_hide()
-        rootconfig.cfgimpl_disable()
-        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_hide()
-        rootconfig.cfgimpl_disable() 
-        rootconfig._cfgimpl_mandatory = False
-    # ____________________________________________________________
     def getkey(self):
         return self._cfgimpl_descr.getkey(self)
 
@@ -467,76 +334,184 @@ class Config(object):
         return hash(self.getkey())
 
     def __eq__(self, other):
+        "Config comparison"
+        if not isinstance(other, Config):
+            return False
         return self.getkey() == other.getkey()
 
     def __ne__(self, other):
+        "Config comparison"
         return not self == other
-
+    # ______________________________________________________________________
     def __iter__(self):
-        # iteration only on Options (not OptionDescriptions)
+        """Pythonesque way of parsing group's ordered options.
+        iteration only on Options (not OptionDescriptions)"""
         for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
+            if not isinstance(child, OptionDescription):
                 try:
                     yield child._name, getattr(self, child._name)
                 except:
-                    pass # hidden, disabled option group
+                    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:
+        """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))
-            groups = [group_type]
         for child in self._cfgimpl_descr._children:
             if isinstance(child, OptionDescription):
-                    try:
-                        if child.get_group_type() in groups: 
+                try:
+                    if group_type is not None:
+                        if child.get_group_type() == group_type:
                             yield child._name, getattr(self, child._name)
-                    except:
-                        pass # hidden, disabled option
-                    
-    def __str__(self, indent=""):
+                    else:
+                        yield child._name, getattr(self, child._name)
+                except:
+                    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:
+                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 
+        """returns a list of all paths in self, recursively, taking care of
         the context of properties (hidden/disabled)
+
+        :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):
-            try: 
+            try:
                 value = getattr(self, path)
-                
+
             except MandatoryError:
                 if mandatory or allpaths:
                     paths.append(path)
             except PropertiesOptionError:
                 if allpaths:
-                    paths.append(path) # hidden or disabled or mandatory option added
+                    paths.append(path) # option which have properties added
             else:
                  paths.append(path)
-        return paths 
-        
+        return paths
+
+    def _find(self, bytype, byname, byvalue, byattrs, first):
+        """
+        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_attrs():
+            if byattrs is None:
+                return True
+            for key, value in byattrs.items():
+                if not hasattr(option, key):
+                    return False
+                else:
+                    if getattr(option, key) != value:
+                        return False
+                    else:
+                        continue
+            return True
+        def _filter_by_name():
+            if byname is None:
+                return True
+            pathname = path.split('.')[-1]
+            if pathname == byname:
+                return True
+            else:
+                return False
+        def _filter_by_value():
+            if byvalue is None:
+                return True
+            try:
+                value = getattr(self, path)
+                if value == byvalue:
+                    return True
+            except:  # 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
+
+        find_results = []
+        paths = self.getpaths(allpaths=True)
+        for path in paths:
+            try:
+                option = self.unwrap_from_path(path)
+            except PropertiesOptionError, err:
+                continue
+            if not _filter_by_name():
+                continue
+            if not _filter_by_value():
+                continue
+            if not _filter_by_type():
+                continue
+            if not _filter_by_attrs():
+                continue
+            if first:
+                return option
+            else:
+                find_results.append(option)
+
+        if find_results == []:
+            raise NotFoundError("no option found in config with these criteria")
+        else:
+            return find_results
+
+    def find(self, bytype=None, byname=None, byvalue=None, byattrs=None):
+        """
+            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
+            :param byattrs: dict of option attributes (default, callback...)
+            :returns: list of matching Option objects
+        """
+        return self._find(bytype, byname, byvalue, byattrs, first=False)
+
+    def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None):
+        """
+            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
+            :param byattrs: dict of option attributes (default, callback...)
+            :returns: list of matching Option objects
+        """
+        return self._find(bytype, byname, byvalue, byattrs, first=True)
+
+
 def make_dict(config, flatten=False):
+    """export the whole config into a `dict`
+    :returns: dict of Option's name (or path) and values"""
     paths = config.getpaths()
     pathsvalues = []
     for path in paths:
@@ -545,21 +520,28 @@ def make_dict(config, flatten=False):
         else:
             pathname = path
         try:
-            value = getattr(config, path)            
-            pathsvalues.append((pathname, value))      
+            value = getattr(config, path)
+            pathsvalues.append((pathname, value))
         except:
-            pass # this just a hidden or disabled option
+            pass  # this just a hidden or disabled option
     options = dict(pathsvalues)
     return options
 
+
 def mandatory_warnings(config):
-    mandatory = config._cfgimpl_get_toplevel()._cfgimpl_mandatory
-    config._cfgimpl_get_toplevel()._cfgimpl_mandatory = True
+    """convenience function to trace Options that are mandatory and
+    where no value has been set
+
+    :returns: generator of mandatory Option's path
+    FIXME : CAREFULL : not multi-user
+    """
+    mandatory = config._cfgimpl_context._cfgimpl_settings.mandatory
+    config._cfgimpl_context._cfgimpl_settings.mandatory = True
     for path in config._cfgimpl_descr.getpaths(include_groups=True):
         try:
-            value = getattr(config, path)
+            value = config._getattr(path, permissive=True)
         except MandatoryError:
             yield path
         except PropertiesOptionError:
             pass
-    config._cfgimpl_get_toplevel()._cfgimpl_mandatory = mandatory
+    config._cfgimpl_context._cfgimpl_settings.mandatory = mandatory