in find_firsts get option only one time
[tiramisu.git] / tiramisu / config.py
index 591eb68..ac4048d 100644 (file)
@@ -1,36 +1,41 @@
 # -*- coding: utf-8 -*-
-"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
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 # The original `Config` design model is unproudly borrowed from
 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
+"options handler global entry point"
 import weakref
 from tiramisu.error import PropertiesOptionError, ConfigError
-from tiramisu.option import OptionDescription, Option, SymLinkOption
-from tiramisu.setting import groups, Settings, default_encoding
-from tiramisu.storage import get_storages
-from tiramisu.value import Values
+from tiramisu.option import OptionDescription, Option, SymLinkOption, \
+    DynSymLinkOption
+from tiramisu.setting import groups, Settings, default_encoding, undefined
+from tiramisu.storage import get_storages, get_storage, set_storage, \
+    _impl_getstate_setting
+from tiramisu.value import Values, Multi
 from tiramisu.i18n import _
 
 
 class SubConfig(object):
-    "sub configuration management entry"
+    """Sub configuration management entry.
+    Tree if OptionDescription's responsability. SubConfig are generated
+    on-demand. A Config is also a SubConfig.
+    Root Config is call context below
+    """
     __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
 
     def __init__(self, descr, context, subpath=None):
@@ -43,71 +48,83 @@ class SubConfig(object):
         :type subpath: `str` with the path name
         """
         # main option description
-        if descr is not None and not isinstance(descr, OptionDescription):
+        error = False
+        try:
+            if descr is not None and not descr.impl_is_optiondescription():  # pragma: optional cover
+                error = True
+        except AttributeError:
+            error = True
+        if error:
             raise TypeError(_('descr must be an optiondescription, not {0}'
                               ).format(type(descr)))
         self._impl_descr = descr
         # sub option descriptions
-        if not isinstance(context, weakref.ReferenceType):
+        if not isinstance(context, weakref.ReferenceType):  # pragma: optional cover
             raise ValueError('context must be a Weakref')
         self._impl_context = context
         self._impl_path = subpath
 
     def cfgimpl_reset_cache(self, only_expired=False, only=('values',
                                                             'settings')):
-        self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
+        "remove cache (in context)"
+        self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)  # pragma: optional cover
 
-    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):
         """:returns: tuple (config, name)"""
         path = path.split('.')
         for step in path[:-1]:
-            self = self._getattr(step,
-                                 force_permissive=force_permissive,
-                                 force_properties=force_properties)
+            self = self.getattr(step,
+                                force_permissive=force_permissive)
         return self, path[-1]
 
-    def __hash__(self):
-        return hash(self.cfgimpl_get_description().impl_getkey(self))
-
-    def __eq__(self, other):
-        "Config's comparison"
-        if not isinstance(other, Config):
-            return False
-        return self.cfgimpl_get_description().impl_getkey(self) == \
-            other.cfgimpl_get_description().impl_getkey(other)
-
-    def __ne__(self, other):
-        "Config's comparison"
-        if not isinstance(other, Config):
-            return True
-        return not self == other
+    #def __hash__(self):
+    #FIXME
+    #    return hash(self.cfgimpl_get_description().impl_getkey(self))
+
+    #def __eq__(self, other):
+    #FIXME
+    #    "Config's comparison"
+    #    if not isinstance(other, Config):
+    #        return False
+    #    return self.cfgimpl_get_description().impl_getkey(self) == \
+    #        other.cfgimpl_get_description().impl_getkey(other)
+
+    #def __ne__(self, other):
+    #FIXME
+    #    "Config's comparison"
+    #    if not isinstance(other, Config):
+    #        return True
+    #    return not self == other
 
     # ______________________________________________________________________
-    def __iter__(self):
+    def __iter__(self, force_permissive=False):
         """Pythonesque way of parsing group's ordered options.
         iteration only on Options (not OptionDescriptions)"""
-        for child in self.cfgimpl_get_description().impl_getchildren():
-            if not isinstance(child, OptionDescription):
+        for child in self.cfgimpl_get_description()._impl_getchildren(
+                context=self._cfgimpl_get_context()):
+            if not child.impl_is_optiondescription():
                 try:
-                    yield child._name, getattr(self, child._name)
-                except GeneratorExit:
+                    name = child.impl_getname()
+                    yield name, self.getattr(name,
+                                             force_permissive=force_permissive)
+                except GeneratorExit:  # pragma: optional cover
                     raise StopIteration
-                except PropertiesOptionError:
+                except PropertiesOptionError:  # pragma: optional cover
                     pass  # option with properties
 
-    def iter_all(self):
+    def iter_all(self, force_permissive=False):
         """A way of parsing options **and** groups.
         iteration on Options and OptionDescriptions."""
         for child in self.cfgimpl_get_description().impl_getchildren():
             try:
-                yield child._name, getattr(self, child._name)
-            except GeneratorExit:
+                yield child.impl_getname(), self.getattr(child.impl_getname(),
+                                                         force_permissive=force_permissive)
+            except GeneratorExit:  # pragma: optional cover
                 raise StopIteration
-            except PropertiesOptionError:
+            except PropertiesOptionError:  # pragma: optional cover
                 pass  # option with properties
 
-    def iter_groups(self, group_type=None):
+    def iter_groups(self, group_type=None, force_permissive=False):
         """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).
@@ -117,18 +134,20 @@ class SubConfig(object):
                            `setting.groups`
         """
         if group_type is not None and not isinstance(group_type,
-                                                     groups.GroupType):
+                                                     groups.GroupType):  # pragma: optional cover
             raise TypeError(_("unknown group_type: {0}").format(group_type))
-        for child in self.cfgimpl_get_description().impl_getchildren():
-            if isinstance(child, OptionDescription):
+        for child in self.cfgimpl_get_description()._impl_getchildren(
+                context=self._cfgimpl_get_context()):
+            if child.impl_is_optiondescription():
                 try:
                     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:
+                        name = child.impl_getname()
+                        yield name, self.getattr(name, force_permissive=force_permissive)
+                except GeneratorExit:  # pragma: optional cover
                     raise StopIteration
-                except PropertiesOptionError:
+                except PropertiesOptionError:  # pragma: optional cover
                     pass
     # ______________________________________________________________________
 
@@ -140,22 +159,28 @@ class SubConfig(object):
         for name, value in self:
             try:
                 lines.append("{0} = {1}".format(name, value))
-            except UnicodeEncodeError:
+            except UnicodeEncodeError:  # pragma: optional cover
                 lines.append("{0} = {1}".format(name,
                                                 value.encode(default_encoding)))
-            except PropertiesOptionError:
-                pass
         return '\n'.join(lines)
 
     __repr__ = __str__
 
     def _cfgimpl_get_context(self):
-        return self._impl_context()
+        """context could be None, we need to test it
+        context is None only if all reference to `Config` object is deleted
+        (for example we delete a `Config` and we manipulate a reference to
+        old `SubConfig`, `Values`, `Multi` or `Settings`)
+        """
+        context = self._impl_context()
+        if context is None:  # pragma: optional cover
+            raise ConfigError(_('the context does not exist anymore'))
+        return context
 
     def cfgimpl_get_description(self):
-        if self._impl_descr is None:
+        if self._impl_descr is None:  # pragma: optional cover
             raise ConfigError(_('no option description found for this config'
-                                ' (may be metaconfig without meta)'))
+                                ' (may be GroupConfig)'))
         else:
             return self._impl_descr
 
@@ -169,38 +194,51 @@ class SubConfig(object):
     # attribute methods
     def __setattr__(self, name, value):
         "attribute notation mechanism for the setting of the value of an option"
-        if name.startswith('_impl_'):
-            object.__setattr__(self, name, value)
-            return
         self._setattr(name, value)
 
     def _setattr(self, name, value, force_permissive=False):
-        if '.' in name:
+        if name.startswith('_impl_'):
+            object.__setattr__(self, name, value)
+            return
+        if '.' in name:  # pragma: optional cover
             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:
-                path = self._impl_path + '.' + name
-            self.cfgimpl_get_values().setitem(child, value, path,
-                                              force_permissive=force_permissive)
-        else:
-            context = self._cfgimpl_get_context()
+            return homeconfig._setattr(name, value, force_permissive)
+        context = self._cfgimpl_get_context()
+        child = self.cfgimpl_get_description().__getattr__(name,
+                                                           context=context)
+        if isinstance(child, OptionDescription):
+            raise TypeError(_("can't assign to an OptionDescription"))  # pragma: optional cover
+        elif isinstance(child, SymLinkOption) and \
+                not isinstance(child, DynSymLinkOption):  # pragma: no dynoptiondescription cover
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
-                child._opt)
+                child._impl_getopt())
             context._setattr(path, value, force_permissive=force_permissive)
+        else:
+            subpath = self._get_subpath(name)
+            self.cfgimpl_get_values().setitem(child, value, subpath,
+                                              force_permissive=force_permissive)
 
     def __delattr__(self, name):
-        child = getattr(self.cfgimpl_get_description(), name)
+        context = self._cfgimpl_get_context()
+        child = self.cfgimpl_get_description().__getattr__(name, context)
         self.cfgimpl_get_values().__delitem__(child)
 
     def __getattr__(self, name):
-        return self._getattr(name)
+        return self.getattr(name)
+
+    def _getattr(self, name, force_permissive=False, validate=True):  # pragma: optional cover
+        """use getattr instead of _getattr
+        """
+        return self.getattr(name, force_permissive, validate)
+
+    def _get_subpath(self, name):
+        if self._impl_path is None:
+            subpath = name
+        else:
+            subpath = self._impl_path + '.' + name
+        return subpath
 
-    def _getattr(self, name, force_permissive=False, force_properties=None,
-                 validate=True):
+    def getattr(self, name, force_permissive=False, validate=True):
         """
         attribute notation mechanism for accessing the value of an option
         :param name: attribute name
@@ -211,127 +249,118 @@ class SubConfig(object):
         # 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_get_description(), name)
-        if self._impl_path is None:
-            subpath = name
-        else:
-            subpath = self._impl_path + '.' + name
-        # symlink options
-        if isinstance(opt_or_descr, SymLinkOption):
-            context = self._cfgimpl_get_context()
+                name, force_permissive=force_permissive)
+            return homeconfig.getattr(name, force_permissive=force_permissive,
+                                      validate=validate)
+        context = self._cfgimpl_get_context()
+        opt_or_descr = self.cfgimpl_get_description().__getattr__(
+            name, context=context)
+        subpath = self._get_subpath(name)
+        if isinstance(opt_or_descr, DynSymLinkOption):
+            return self.cfgimpl_get_values()._get_cached_item(
+                opt_or_descr, path=subpath,
+                validate=validate,
+                force_permissive=force_permissive)
+        elif isinstance(opt_or_descr, SymLinkOption):  # pragma: no dynoptiondescription cover
             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):
+                opt_or_descr._impl_getopt())
+            return context.getattr(path, validate=validate,
+                                   force_permissive=force_permissive)
+        elif opt_or_descr.impl_is_optiondescription():
             self.cfgimpl_get_settings().validate_properties(
                 opt_or_descr, True, False, path=subpath,
-                force_permissive=force_permissive,
-                force_properties=force_properties)
+                force_permissive=force_permissive)
             return SubConfig(opt_or_descr, self._impl_context, subpath)
         else:
-            return self.cfgimpl_get_values().getitem(
+            return self.cfgimpl_get_values()._get_cached_item(
                 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'):
+    def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
+             check_properties=True, force_permissive=False):
         """
             finds a list of options recursively in the config
 
             :param bytype: Option class (BoolOption, StrOption, ...)
-            :param byname: filter by Option._name
+            :param byname: filter by Option.impl_getname()
             :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()
-                                                 )
+                                                 _subpath=self.cfgimpl_get_path(False),
+                                                 check_properties=check_properties,
+                                                 force_permissive=force_permissive)
 
-    def find_first(self, bytype=None, byname=None, byvalue=None,
-                   type_='option', display_error=True):
+    def find_first(self, bytype=None, byname=None, byvalue=undefined,
+                   type_='option', display_error=True, check_properties=True,
+                   force_permissive=False):
         """
             finds an option recursively in the config
 
             :param bytype: Option class (BoolOption, StrOption, ...)
-            :param byname: filter by Option._name
+            :param byname: filter by Option.impl_getname()
             :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(), display_error=display_error)
+            _subpath=self.cfgimpl_get_path(False), display_error=display_error,
+            check_properties=check_properties,
+            force_permissive=force_permissive)
 
     def _find(self, bytype, byname, byvalue, first, type_='option',
-              _subpath=None, check_properties=True, display_error=True):
+              _subpath=None, check_properties=True, display_error=True,
+              force_permissive=False, only_path=undefined,
+              only_option=undefined):
         """
         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():
-            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:
+            if byvalue is undefined:
                 return True
             try:
-                value = getattr(self, path)
-                if value == byvalue:
-                    return True
-            except PropertiesOptionError:  # a property is a restriction
-                                           # upon 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
+                value = self.getattr(path, force_permissive=force_permissive)
+                if isinstance(value, Multi):
+                    return byvalue in value
+                else:
+                    return value == byvalue
+            # a property is a restriction upon the access of the value
+            except PropertiesOptionError:  # pragma: optional cover
+                return False
 
-        if type_ not in ('option', 'path', 'value'):
+        if type_ not in ('option', 'path', 'value'):  # pragma: optional cover
             raise ValueError(_('unknown type_ type {0}'
                                'for _find').format(type_))
         find_results = []
-        opts, paths = self.cfgimpl_get_description()._cache_paths
-        for index in range(0, len(paths)):
-            option = opts[index]
-            if isinstance(option, OptionDescription):
-                continue
-            path = paths[index]
-            if _subpath is not None and not path.startswith(_subpath + '.'):
-                continue
-            if not _filter_by_name():
-                continue
+        # if value and/or check_properties are set, need all avalaible option
+        # If first one has no good value or not good property check second one
+        # and so on
+        only_first = first is True and byvalue is None and \
+            check_properties is None
+        if only_path is not undefined:
+            options = [(only_path, only_option)]
+        else:
+            options = self.cfgimpl_get_description().impl_get_options_paths(
+                bytype, byname, _subpath, only_first,
+                self._cfgimpl_get_context())
+        for path, option in options:
             if not _filter_by_value():
                 continue
             #remove option with propertyerror, ...
-            if check_properties:
+            if byvalue is undefined and check_properties:
                 try:
-                    value = getattr(self, path)
-                except PropertiesOptionError:
+                    value = self.getattr(path,
+                                         force_permissive=force_permissive)
+                except PropertiesOptionError:  # pragma: optional cover
                     # a property restricts the access of the value
                     continue
-            if not _filter_by_type():
-                continue
             if type_ == 'value':
                 retval = value
             elif type_ == 'path':
@@ -345,7 +374,7 @@ class SubConfig(object):
         return self._find_return_results(find_results, display_error)
 
     def _find_return_results(self, find_results, display_error):
-        if find_results == []:
+        if find_results == []:  # pragma: optional cover
             if display_error:
                 raise AttributeError(_("no option found in config"
                                        " with these criteria"))
@@ -356,7 +385,7 @@ class SubConfig(object):
             return find_results
 
     def make_dict(self, flatten=False, _currpath=None, withoption=None,
-                  withvalue=None):
+                  withvalue=undefined, force_permissive=False):
         """exports the whole config into a `dict`, for example:
 
         >>> print cfg.make_dict()
@@ -396,74 +425,80 @@ class SubConfig(object):
         pathsvalues = []
         if _currpath is None:
             _currpath = []
-        if withoption is None and withvalue is not None:
+        if withoption is None and withvalue is not undefined:  # pragma: optional cover
             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):
+            context = self._cfgimpl_get_context()
+            for path in context._find(bytype=None, byname=withoption,
+                                      byvalue=withvalue, first=False,
+                                      type_='path', _subpath=self.cfgimpl_get_path(False),
+                                      force_permissive=force_permissive):
                 path = '.'.join(path.split('.')[:-1])
-                opt = self._cfgimpl_get_context().cfgimpl_get_description(
-                ).impl_get_opt_by_path(path)
+                opt = context.unwrap_from_path(path, force_permissive=True)
+                mypath = self.cfgimpl_get_path()
                 if mypath is not None:
                     if mypath == path:
                         withoption = None
-                        withvalue = None
+                        withvalue = undefined
                         break
                     else:
                         tmypath = mypath + '.'
-                        if not path.startswith(tmypath):
+                        if not path.startswith(tmypath):  # pragma: optional cover
                             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)
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
+                                    force_permissive=force_permissive)
         #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)
+                path = opt.impl_getname()
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten,
+                                    force_permissive=force_permissive)
         if _currpath == []:
             options = dict(pathsvalues)
             return options
         return pathsvalues
 
-    def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten):
-        if isinstance(opt, OptionDescription):
-            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)
+    def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
+                       force_permissive=False):
+        try:
+            if opt.impl_is_optiondescription():
+                pathsvalues += self.getattr(path,
+                                            force_permissive=force_permissive).make_dict(
+                                                flatten,
+                                                _currpath + path.split('.'),
+                                                force_permissive=force_permissive)
+            else:
+                value = self.getattr(opt.impl_getname(),
+                                     force_permissive=force_permissive)
                 if flatten:
-                    name = opt._name
+                    name = opt.impl_getname()
                 else:
-                    name = '.'.join(_currpath + [opt._name])
+                    name = '.'.join(_currpath + [opt.impl_getname()])
                 pathsvalues.append((name, value))
-            except PropertiesOptionError:
-                pass  # this just a hidden or disabled option
+        except PropertiesOptionError:  # pragma: optional cover
+            pass
 
-    def cfgimpl_get_path(self):
+    def cfgimpl_get_path(self, dyn=True):
         descr = self.cfgimpl_get_description()
-        context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
-        return context_descr.impl_get_path_by_opt(descr)
+        if not dyn and descr.impl_is_dynoptiondescription():
+            context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
+            return context_descr.impl_get_path_by_opt(descr._impl_getopt())
+        return self._impl_path
 
 
-class CommonConfig(SubConfig):
-    "abstract base class for the Config and the MetaConfig"
-    __slots__ = ('_impl_values', '_impl_settings', '_impl_meta')
+class _CommonConfig(SubConfig):
+    "abstract base class for the Config, GroupConfig and the MetaConfig"
+    __slots__ = ('_impl_values', '_impl_settings', '_impl_meta', '_impl_test')
 
-    def _impl_build_all_paths(self):
-        self.cfgimpl_get_description().impl_build_cache()
+    def _impl_build_all_caches(self):
+        if not self.cfgimpl_get_description().impl_already_build_caches():
+            self.cfgimpl_get_description().impl_build_cache_consistency()
+            self.cfgimpl_get_description().impl_build_cache_option()
+            self.cfgimpl_get_description().impl_validate_options()
 
     def read_only(self):
         "read only is a global config's setting, see `settings.py`"
@@ -473,14 +508,17 @@ class CommonConfig(SubConfig):
         "read write is a global config's setting, see `settings.py`"
         self.cfgimpl_get_settings().read_write()
 
-    def getowner(self, opt):
+    def getowner(self, opt, force_permissive=False):
         """convenience method to retrieve an option's owner
         from the config itself
         """
-        if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption):
+        if not isinstance(opt, Option) and \
+                not isinstance(opt, SymLinkOption) and \
+                not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
             raise TypeError(_('opt in getowner must be an option not {0}'
                               '').format(type(opt)))
-        return self.cfgimpl_get_values().getowner(opt)
+        return self.cfgimpl_get_values().getowner(opt,
+                                                  force_permissive=force_permissive)
 
     def unwrap_from_path(self, path, force_permissive=False):
         """convenience method to extract and Option() object from the Config()
@@ -489,17 +527,19 @@ class CommonConfig(SubConfig):
 
         :returns: Option()
         """
+        context = self._cfgimpl_get_context()
         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)
+            return homeconfig.cfgimpl_get_description().__getattr__(path, context=context)
+        return self.cfgimpl_get_description().__getattr__(path, context=context)
 
-    def cfgimpl_get_path(self):
+    def cfgimpl_get_path(self, dyn=True):
         return None
 
     def cfgimpl_get_meta(self):
-        return self._impl_meta
+        if self._impl_meta is not None:
+            return self._impl_meta()
 
     # information
     def impl_set_information(self, key, value):
@@ -510,18 +550,55 @@ class CommonConfig(SubConfig):
         """
         self._impl_values.set_information(key, value)
 
-    def impl_get_information(self, key, default=None):
+    def impl_get_information(self, key, default=undefined):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
         return self._impl_values.get_information(key, default)
 
+    # ----- state
+    def __getstate__(self):
+        if self._impl_meta is not None:
+            raise ConfigError(_('cannot serialize Config with MetaConfig'))  # pragma: optional cover
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        slots -= frozenset(['_impl_context', '_impl_meta', '__weakref__'])
+        state = {}
+        for slot in slots:
+            try:
+                state[slot] = getattr(self, slot)
+            except AttributeError:  # pragma: optional cover
+                pass
+        storage = self._impl_values._p_._storage
+        if not storage.serializable:
+            raise ConfigError(_('this storage is not serialisable, could be a '
+                              'none persistent storage'))  # pragma: optional cover
+        state['_storage'] = {'session_id': storage.session_id,
+                             'persistent': storage.persistent}
+        state['_impl_setting'] = _impl_getstate_setting()
+        return state
+
+    def __setstate__(self, state):
+        for key, value in state.items():
+            if key not in ['_storage', '_impl_setting']:
+                setattr(self, key, value)
+        set_storage('config', **state['_impl_setting'])
+        self._impl_context = weakref.ref(self)
+        self._impl_settings.context = weakref.ref(self)
+        self._impl_values.context = weakref.ref(self)
+        storage = get_storage('config', test=self._impl_test, **state['_storage'])
+        self._impl_values._impl_setstate(storage)
+        self._impl_settings._impl_setstate(storage)
+        self._impl_meta = None
+
 
 # ____________________________________________________________
-class Config(CommonConfig):
+class Config(_CommonConfig):
     "main configuration management entry"
-    __slots__ = ('__weakref__', )
+    __slots__ = ('__weakref__', '_impl_test')
 
     def __init__(self, descr, session_id=None, persistent=False):
         """ Configuration option management master class
@@ -540,8 +617,10 @@ class Config(CommonConfig):
         self._impl_settings = Settings(self, settings)
         self._impl_values = Values(self, values)
         super(Config, self).__init__(descr, weakref.ref(self))
-        self._impl_build_all_paths()
+        self._impl_build_all_caches()
         self._impl_meta = None
+        #undocumented option used only in test script
+        self._impl_test = False
 
     def cfgimpl_reset_cache(self,
                             only_expired=False,
@@ -552,42 +631,29 @@ class Config(CommonConfig):
             self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
 
 
-class MetaConfig(CommonConfig):
+class GroupConfig(_CommonConfig):
     __slots__ = ('_impl_children', '__weakref__')
 
-    def __init__(self, children, meta=True, session_id=None, persistent=False):
+    def __init__(self, children, session_id=None, persistent=False,
+                 _descr=None):
         if not isinstance(children, list):
             raise ValueError(_("metaconfig's children must be a list"))
-        descr = None
-        if meta:
-            for child in children:
-                if not isinstance(child, CommonConfig):
-                    raise TypeError(_("metaconfig's children "
-                                      "must be config, not {0}"
-                                      ).format(type(child)))
-                if descr is None:
-                    descr = child.cfgimpl_get_description()
-                elif not 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
-
         self._impl_children = children
         settings, values = get_storages(self, session_id, persistent)
         self._impl_settings = Settings(self, settings)
         self._impl_values = Values(self, values)
-        super(MetaConfig, self).__init__(descr, weakref.ref(self))
+        super(GroupConfig, self).__init__(_descr, weakref.ref(self))
         self._impl_meta = None
+        #undocumented option used only in test script
+        self._impl_test = False
 
     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_get_context(self):
+    #    "a meta config is a config which has a setting, that is itself"
+    #    return self
+    #
     def cfgimpl_reset_cache(self,
                             only_expired=False,
                             only=('values', 'settings')):
@@ -598,69 +664,87 @@ class MetaConfig(CommonConfig):
         for child in self._impl_children:
             child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
 
-    def set_contexts(self, path, value):
+    def setattrs(self, path, value):
+        """Setattr not in current GroupConfig, but in each children
+        """
         for child in self._impl_children:
             try:
-                if not isinstance(child, MetaConfig):
+                if not isinstance(child, GroupConfig):
                     setattr(child, path, value)
                 else:
-                    child.set_contexts(path, value)
+                    child.setattrs(path, value)
             except PropertiesOptionError:
                 pass
 
-    def find_first_contexts(self, byname=None, bypath=None, byvalue=None,
-                            type_='path', display_error=True):
+    def find_firsts(self, byname=None, bypath=undefined, byoption=undefined,
+                    byvalue=undefined, type_='option', display_error=True):
+        """Find first not in current GroupConfig, but in each children
+        """
         ret = []
+        #if MetaConfig, all children have same OptionDescription in context
+        #so search only one time the option for all children
         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,
+            if bypath is undefined and byname is not None and \
+                    isinstance(self, MetaConfig):
+                bypath = self._find(bytype=None, byvalue=undefined, byname=byname,
                                     first=True, type_='path',
                                     check_properties=False,
                                     display_error=display_error)
-        except ConfigError:
-            pass
+                byname = None
+                byoption = self.cfgimpl_get_description(
+                ).impl_get_opt_by_path(bypath)
+        except AttributeError:
+            return self._find_return_results([], True)
         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))
+                if isinstance(child, GroupConfig):
+                    ret.extend(child.find_firsts(byname=byname,
+                                                 bypath=bypath,
+                                                 byoption=byoption,
+                                                 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))
+                    if type_ == 'config':
+                        f_type = 'path'
+                    else:
+                        f_type = type_
+                    f_ret = child._find(None, byname, byvalue, first=True,
+                                        type_=f_type, display_error=False,
+                                        only_path=bypath,
+                                        only_option=byoption)
+                    if type_ == 'config':
+                        ret.append(child)
+                    else:
+                        ret.append(f_ret)
             except AttributeError:
                 pass
         return self._find_return_results(ret, display_error)
 
+    def __repr__(self):
+        return object.__repr__(self)
 
-def mandatory_warnings(config):
-    """convenience function to trace Options that are mandatory and
-    where no value has been set
+    def __str__(self):
+        return object.__str__(self)
 
-    :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().impl_getpaths(
-            include_groups=True):
-        try:
-            config._getattr(path, force_properties=frozenset(('mandatory',)))
-        except PropertiesOptionError as err:
-            if err.proptype == ['mandatory']:
-                yield path
-    config.cfgimpl_reset_cache(only=('values',))
+class MetaConfig(GroupConfig):
+    __slots__ = tuple()
+
+    def __init__(self, children, session_id=None, persistent=False):
+        descr = None
+        for child in children:
+            if not isinstance(child, _CommonConfig):
+                raise TypeError(_("metaconfig's children "
+                                  "should be config, not {0}"
+                                  ).format(type(child)))
+            if child.cfgimpl_get_meta() is not None:
+                raise ValueError(_("child has already a metaconfig's"))
+            if descr is None:
+                descr = child.cfgimpl_get_description()
+            elif not descr is child.cfgimpl_get_description():
+                raise ValueError(_('all config in metaconfig must '
+                                   'have the same optiondescription'))
+            child._impl_meta = weakref.ref(self)
+
+        super(MetaConfig, self).__init__(children, session_id, persistent, descr)