work on MetaConfig
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 2 May 2013 09:34:57 +0000 (11:34 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 2 May 2013 09:34:57 +0000 (11:34 +0200)
test/test_metaconfig.py [new file with mode: 0644]
test/test_permissive.py
tiramisu/config.py
tiramisu/error.py
tiramisu/option.py
tiramisu/value.py

diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py
new file mode 100644 (file)
index 0000000..3e2c924
--- /dev/null
@@ -0,0 +1,166 @@
+#this test is much more to test that **it's there** and answers attribute access
+import autopath
+
+from py.test import raises
+
+from tiramisu.setting import owners
+from tiramisu.config import Config, MetaConfig
+from tiramisu.option import IntOption, OptionDescription
+from tiramisu.error import ConfigError
+
+owners.add_owner('meta')
+
+
+def make_description():
+    i1 = IntOption('i1', '')
+    i2 = IntOption('i2', '', default=1)
+    i3 = IntOption('i3', '')
+    i4 = IntOption('i4', '', default=2)
+    od1 = OptionDescription('od1', '', [i1, i2, i3, i4])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2)
+    conf2 = Config(od2)
+    meta = MetaConfig([conf1, conf2])
+    meta.cfgimpl_get_settings().setowner(owners.meta)
+    return meta
+
+
+#FIXME ne pas mettre 2 meta dans une config
+#FIXME ne pas mettre 2 OD differents dans un meta
+def test_none():
+    meta = make_description()
+    conf1, conf2 = meta._cfgimpl_children
+    assert conf1.od1.i3 is conf2.od1.i3 is None
+    assert conf1.getowner('od1.i3') is conf2.getowner('od1.i3') is owners.default
+    meta.od1.i3 = 3
+    assert conf1.od1.i3 == conf2.od1.i3 == 3
+    assert conf1.getowner('od1.i3') is conf2.getowner('od1.i3') is owners.meta
+    meta.od1.i3 = 3
+    conf1.od1.i3 = 2
+    assert conf1.od1.i3 == 2
+    assert conf2.od1.i3 == 3
+    assert conf1.getowner('od1.i3') is owners.user
+    assert conf2.getowner('od1.i3') is owners.meta
+    meta.od1.i3 = 4
+    assert conf1.od1.i3 == 2
+    assert conf2.od1.i3 == 4
+    assert conf1.getowner('od1.i3') is owners.user
+    assert conf2.getowner('od1.i3') is owners.meta
+    del(meta.od1.i3)
+    assert conf1.od1.i3 == 2
+    assert conf2.od1.i3 is None
+    assert conf1.getowner('od1.i3') is owners.user
+    assert conf2.getowner('od1.i3') is owners.default
+    del(conf1.od1.i3)
+    assert conf1.od1.i3 is conf2.od1.i3 is None
+    assert conf1.getowner('od1.i3') is conf2.getowner('od1.i3') is owners.default
+
+
+def test_default():
+    meta = make_description()
+    conf1, conf2 = meta._cfgimpl_children
+    assert conf1.od1.i2 == conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.default
+    meta.od1.i2 = 3
+    assert conf1.od1.i2 == conf2.od1.i2 == 3
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.meta
+    meta.od1.i2 = 3
+    conf1.od1.i2 = 2
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 3
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.meta
+    meta.od1.i2 = 4
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 4
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.meta
+    del(meta.od1.i2)
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.default
+    del(conf1.od1.i2)
+    assert conf1.od1.i2 == conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.default
+
+
+def test_contexts():
+    meta = make_description()
+    conf1, conf2 = meta._cfgimpl_children
+    assert conf1.od1.i2 == conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.default
+    meta.set_contexts('od1.i2', 6)
+    assert meta.od1.i2 == 1
+    assert conf1.od1.i2 == conf2.od1.i2 == 6
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.user
+
+
+def test_find():
+    meta = make_description()
+    i2 = meta.unwrap_from_path('od1.i2')
+    assert [i2] == meta.find(byname='i2')
+    assert i2 == meta.find_first(byname='i2')
+    assert meta.make_dict() == {'od1.i4': 2, 'od1.i1': None, 'od1.i3': None, 'od1.i2': 1}
+
+
+def test_meta_meta():
+    meta1 = make_description()
+    meta2 = MetaConfig([meta1])
+    meta2.cfgimpl_get_settings().setowner(owners.meta)
+    conf1, conf2 = meta1._cfgimpl_children
+    assert conf1.od1.i2 == conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.default
+    meta2.od1.i2 = 3
+    assert conf1.od1.i2 == conf2.od1.i2 == 3
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.meta
+    meta2.od1.i2 = 3
+    conf1.od1.i2 = 2
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 3
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.meta
+    meta2.od1.i2 = 4
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 4
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.meta
+    del(meta2.od1.i2)
+    assert conf1.od1.i2 == 2
+    assert conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is owners.user
+    assert conf2.getowner('od1.i2') is owners.default
+    del(conf1.od1.i2)
+    assert conf1.od1.i2 == conf2.od1.i2 == 1
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.default
+    meta1.od1.i2 = 6
+    assert conf1.od1.i2 == conf2.od1.i2 == 6
+    assert conf1.getowner('od1.i2') is conf2.getowner('od1.i2') is owners.meta
+
+
+def test_meta_meta_set():
+    meta1 = make_description()
+    meta2 = MetaConfig([meta1])
+    meta2.cfgimpl_get_settings().setowner(owners.meta)
+    conf1, conf2 = meta1._cfgimpl_children
+    meta2.set_contexts('od1.i1', 7)
+    assert conf1.od1.i1 == conf2.od1.i1 == 7
+    assert conf1.getowner('od1.i1') is conf2.getowner('od1.i1') is owners.user
+    assert [conf1, conf2] == meta2.find_first_contexts(byname='i1', byvalue=7)
+    conf1.od1.i1 = 8
+    assert [conf2] == meta2.find_first_contexts(byname='i1', byvalue=7)
+    assert [conf1] == meta2.find_first_contexts(byname='i1', byvalue=8)
+
+
+def test_not_meta():
+    i1 = IntOption('i1', '')
+    od1 = OptionDescription('od1', '', [i1])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2)
+    conf2 = Config(od2)
+    meta = MetaConfig([conf1, conf2], False)
+    raises(ConfigError, 'meta.od1.i1')
+    conf1, conf2 = meta._cfgimpl_children
+    meta.set_contexts('od1.i1', 7)
+    assert conf1.od1.i1 == conf2.od1.i1 == 7
+    assert conf1.getowner('od1.i1') is conf2.getowner('od1.i1') is owners.user
index 6815316..783afde 100644 (file)
@@ -71,7 +71,7 @@ def test_permissive_frozen():
         config.u1 = 1
     except PropertiesOptionError, err:
         props = err.proptype
-    assert props == ['disabled', 'frozen']
+    assert props == ['frozen', 'disabled']
     setting.append('permissive')
     config.u1 = 1
     assert config.u1 == 1
@@ -80,4 +80,4 @@ def test_permissive_frozen():
         config.u1 = 1
     except PropertiesOptionError, err:
         props = err.proptype
-    assert props == ['disabled', 'frozen']
+    assert props == ['frozen', 'disabled']
index d4742fd..c98daa9 100644 (file)
@@ -21,7 +21,7 @@
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 #from inspect import getmembers, ismethod
-from tiramisu.error import PropertiesOptionError
+from tiramisu.error import PropertiesOptionError, ConfigError
 from tiramisu.option import OptionDescription, Option, SymLinkOption
 from tiramisu.setting import groups, Setting
 from tiramisu.value import Values
@@ -30,7 +30,7 @@ from tiramisu.i18n import _
 
 class SubConfig(object):
     "sub configuration management entry"
-    __slots__ = ('_cfgimpl_descr', '_cfgimpl_context')
+    __slots__ = ('_cfgimpl_context', '_cfgimpl_descr')
 
     def __init__(self, descr, context):
         """ Configuration option management master class
@@ -42,101 +42,23 @@ class SubConfig(object):
         """
         # main option description
         if not isinstance(descr, OptionDescription):
-            raise ValueError(_('descr must be an optiondescription, not {0}').format(type(descr)))
+            raise ValueError(_('descr must be an optiondescription, not {0}'
+                               '').format(type(descr)))
         self._cfgimpl_descr = descr
         # sub option descriptions
+        if not isinstance(context, SubConfig):
+            raise ValueError('context must be a SubConfig')
         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_reset_cache(self, only_expired=False, only=('values',
+                                                            'settings')):
+        self.cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
 
     def cfgimpl_get_consistancies(self):
         return self.cfgimpl_get_context().cfgimpl_get_description()._consistancies
 
-    def cfgimpl_get_description(self):
-        return self._cfgimpl_descr
-
-    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
-        self.cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
-
-    # ____________________________________________________________
-    # attribute methods
-    def __setattr__(self, name, value):
-        "attribute notation mechanism for the setting of the value of an option"
-        if name.startswith('_cfgimpl_'):
-            #self.__dict__[name] = value
-            object.__setattr__(self, name, value)
-            return
-        self._setattr(name, value)
-
-    def _setattr(self, name, value, force_permissive=False):
-        if '.' in name:
-            homeconfig, name = self.cfgimpl_get_home_by_path(name)
-            return homeconfig.__setattr__(name, value)
-        child = getattr(self._cfgimpl_descr, name)
-        if not isinstance(child, SymLinkOption):
-            self.cfgimpl_get_values().setitem(child, value,
-                                              force_permissive=force_permissive)
-        else:
-            context = self.cfgimpl_get_context()
-            path = context.cfgimpl_get_description().optimpl_get_path_by_opt(child._opt)
-            context._setattr(path, value, force_permissive=force_permissive)
-
-    def __delattr__(self, name):
-        child = getattr(self._cfgimpl_descr, name)
-        del(self.cfgimpl_get_values()[child])
-
-    def __getattr__(self, name):
-        return self._getattr(name)
-
-    def _getattr(self, name, force_permissive=False, force_properties=None,
-                 validate=True):
-        """
-        attribute notation mechanism for accessing the value of an option
-        :param name: attribute name
-        :return: option's value if name is an option name, OptionDescription
-                 otherwise
-        """
-        # attribute access by passing a path,
-        # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
-        if '.' in name:
-            homeconfig, name = self.cfgimpl_get_home_by_path(name,
-                                                             force_permissive=force_permissive,
-                                                             force_properties=force_properties)
-            return homeconfig._getattr(name, force_permissive=force_permissive,
-                                       force_properties=force_properties,
-                                       validate=validate)
-        # special attributes
-        if name.startswith('_cfgimpl_') or name.startswith('cfgimpl_'):
-            # if it were in __dict__ it would have been found already
-            object.__getattr__(self, name)
-        opt_or_descr = getattr(self.cfgimpl_get_description(), name)
-        # symlink options
-        if isinstance(opt_or_descr, SymLinkOption):
-            context = self.cfgimpl_get_context()
-            path = context.cfgimpl_get_description().optimpl_get_path_by_opt(opt_or_descr._opt)
-            return context._getattr(path, validate=validate,
-                                    force_properties=force_properties,
-                                    force_permissive=force_permissive)
-        elif isinstance(opt_or_descr, OptionDescription):
-            self.cfgimpl_get_settings().validate_properties(opt_or_descr,
-                                                            True, False,
-                                                            force_permissive=force_permissive,
-                                                            force_properties=force_properties)
-            return SubConfig(opt_or_descr, self._cfgimpl_context)
-        else:
-            return self.cfgimpl_get_values().getitem(opt_or_descr,
-                                                     validate=validate,
-                                                     force_properties=force_properties,
-                                                     force_permissive=force_permissive)
-
-    def cfgimpl_get_home_by_path(self, path, force_permissive=False, force_properties=None):
+    def cfgimpl_get_home_by_path(self, path, force_permissive=False,
+                                 force_properties=None):
         """:returns: tuple (config, name)"""
         path = path.split('.')
         for step in path[:-1]:
@@ -202,7 +124,8 @@ class SubConfig(object):
             if isinstance(child, OptionDescription):
                 try:
                     if group_type is None or (group_type is not None and
-                                              child.optimpl_get_group_type() == group_type):
+                                              child.optimpl_get_group_type()
+                                              == group_type):
                         yield child._name, getattr(self, child._name)
                 except GeneratorExit:
                     raise StopIteration
@@ -224,10 +147,91 @@ class SubConfig(object):
 
     __repr__ = __str__
 
-    def cfgimpl_get_path(self):
-        descr = self.cfgimpl_get_description()
-        context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
-        return context_descr.optimpl_get_path_by_opt(descr)
+    def cfgimpl_get_context(self):
+        return self._cfgimpl_context
+
+    def cfgimpl_get_description(self):
+        if self._cfgimpl_descr is None:
+            raise ConfigError(_('no optiondescription for this config (may be MetaConfig without meta)'))
+        else:
+            return self._cfgimpl_descr
+
+    def cfgimpl_get_settings(self):
+        return self.cfgimpl_get_context()._cfgimpl_settings
+
+    def cfgimpl_get_values(self):
+        return self.cfgimpl_get_context()._cfgimpl_values
+
+    # ____________________________________________________________
+    # attribute methods
+    def __setattr__(self, name, value):
+        "attribute notation mechanism for the setting of the value of an option"
+        if name.startswith('_cfgimpl_'):
+            #self.__dict__[name] = value
+            object.__setattr__(self, name, value)
+            return
+        self._setattr(name, value)
+
+    def _setattr(self, name, value, force_permissive=False):
+        if '.' in name:
+            homeconfig, name = self.cfgimpl_get_home_by_path(name)
+            return homeconfig.__setattr__(name, value)
+        child = getattr(self.cfgimpl_get_description(), name)
+        if not isinstance(child, SymLinkOption):
+            self.cfgimpl_get_values().setitem(child, value,
+                                              force_permissive=force_permissive)
+        else:
+            context = self.cfgimpl_get_context()
+            path = context.cfgimpl_get_description().optimpl_get_path_by_opt(child._opt)
+            context._setattr(path, value, force_permissive=force_permissive)
+
+    def __delattr__(self, name):
+        child = getattr(self.cfgimpl_get_description(), name)
+        del(self.cfgimpl_get_values()[child])
+
+    def __getattr__(self, name):
+        return self._getattr(name)
+
+    def _getattr(self, name, force_permissive=False, force_properties=None,
+                 validate=True):
+        """
+        attribute notation mechanism for accessing the value of an option
+        :param name: attribute name
+        :return: option's value if name is an option name, OptionDescription
+                 otherwise
+        """
+        # attribute access by passing a path,
+        # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
+        if '.' in name:
+            homeconfig, name = self.cfgimpl_get_home_by_path(name,
+                                                             force_permissive=force_permissive,
+                                                             force_properties=force_properties)
+            return homeconfig._getattr(name, force_permissive=force_permissive,
+                                       force_properties=force_properties,
+                                       validate=validate)
+        # special attributes
+        if name.startswith('_cfgimpl_') or name.startswith('cfgimpl_'):
+            # if it were in __dict__ it would have been found already
+            return object.__getattribute__(self, name)
+        opt_or_descr = getattr(self.cfgimpl_get_description(), name)
+        # symlink options
+        if isinstance(opt_or_descr, SymLinkOption):
+            context = self.cfgimpl_get_context()
+            path = context.cfgimpl_get_description().optimpl_get_path_by_opt(opt_or_descr._opt)
+            return context._getattr(path, validate=validate,
+                                    force_properties=force_properties,
+                                    force_permissive=force_permissive)
+        elif isinstance(opt_or_descr, OptionDescription):
+            self.cfgimpl_get_settings().validate_properties(opt_or_descr,
+                                                            True, False,
+                                                            force_permissive=force_permissive,
+                                                            force_properties=force_properties)
+            return SubConfig(opt_or_descr, self.cfgimpl_get_context())
+        else:
+            return self.cfgimpl_get_values().getitem(opt_or_descr,
+                                                     validate=validate,
+                                                     force_properties=force_properties,
+                                                     force_permissive=force_permissive)
 
     def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
@@ -243,7 +247,8 @@ class SubConfig(object):
                                                 type_=type_,
                                                 _subpath=self.cfgimpl_get_path())
 
-    def find_first(self, bytype=None, byname=None, byvalue=None, type_='option'):
+    def find_first(self, bytype=None, byname=None, byvalue=None,
+                   type_='option'):
         """
             finds an option recursively in the config
 
@@ -257,14 +262,106 @@ class SubConfig(object):
                                                 type_=type_,
                                                 _subpath=self.cfgimpl_get_path())
 
-    def make_dict(self, flatten=False, _currpath=None, withoption=None, withvalue=None):
+    def _find(self, bytype, byname, byvalue, first, type_='option',
+              _subpath=None, check_properties=True):
+        """
+        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:
+                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', 'context', 'value'):
+            raise ValueError(_('unknown type_ type {0} for _find').format(type_))
+        find_results = []
+        opts, paths = self.cfgimpl_get_description()._cache_paths
+        for index in range(0, len(paths)):
+            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, ...
+            if check_properties:
+                try:
+                    value = getattr(self, path)
+                except PropertiesOptionError:
+                    # a property restricts the access of the value
+                    continue
+            if not _filter_by_type():
+                continue
+            #if not _filter_by_attrs():
+            #    continue
+            if type_ == 'value':
+                retval = value
+            elif type_ == 'path':
+                retval = path
+            elif type_ == 'option':
+                retval = option
+            elif type_ == 'context':
+                retval = self.cfgimpl_get_context()
+            if first:
+                return retval
+            else:
+                find_results.append(retval)
+        if find_results == []:
+            #FIXME too slow
+            #raise AttributeError(_("no option found in config with these criteria"))
+            raise AttributeError("no option found in config with these criteria")
+        else:
+            return find_results
+
+    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"))
+            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,
@@ -284,7 +381,8 @@ class SubConfig(object):
                         tmypath = mypath + '.'
                         if not path.startswith(tmypath):
                             raise AttributeError(_('unexpected path {0}, '
-                                                 'should start with {1}').format(path, mypath))
+                                                   '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 !
@@ -315,27 +413,17 @@ class SubConfig(object):
             except PropertiesOptionError:
                 pass  # this just a hidden or disabled option
 
+    def cfgimpl_get_path(self):
+        descr = self.cfgimpl_get_description()
+        context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
+        return context_descr.optimpl_get_path_by_opt(descr)
 
-# ____________________________________________________________
-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()
+class ConfigCommon(SubConfig):
+    __slots__ = ('_cfgimpl_values', '_cfgimpl_settings', '_cfgimpl_meta')
 
     def _cfgimpl_build_all_paths(self):
-        self._cfgimpl_descr.optimpl_build_cache()
+        self.cfgimpl_get_description().optimpl_build_cache()
 
     def read_only(self):
         self.cfgimpl_get_settings().read_only()
@@ -347,12 +435,6 @@ class Config(SubConfig):
         opt = self.cfgimpl_get_description().optimpl_get_opt_by_path(path)
         return self.cfgimpl_get_values().getowner(opt)
 
-    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
@@ -362,140 +444,123 @@ class Config(SubConfig):
         """
         if '.' in path:
             homeconfig, path = self.cfgimpl_get_home_by_path(path)
-            return getattr(homeconfig._cfgimpl_descr, path)
-        return getattr(self._cfgimpl_descr, path)
+            return getattr(homeconfig.cfgimpl_get_description(), path)
+        return getattr(self.cfgimpl_get_description(), path)
 
     def cfgimpl_get_path(self):
         return None
 
-    def _find(self, bytype, byname, byvalue, first, type_='option',
-              _subpath=None, check_properties=True):
-        """
-        convenience method for finding an option that lives only in the subtree
+    def cfgimpl_get_meta(self):
+        return self._cfgimpl_meta
 
-        :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:
-                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
+# ____________________________________________________________
+class Config(ConfigCommon):
+    "main configuration management entry"
+    __slots__ = tuple()
 
-        def _filter_by_type():
-            if bytype is None:
-                return True
-            if isinstance(option, bytype):
-                return True
-            return False
+    def __init__(self, descr):
+        """ Configuration option management master class
 
-        #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, ...
-            if check_properties:
-                try:
-                    value = getattr(self, path)
-                except PropertiesOptionError:
-                    # a property restricts the access of the value
-                    continue
-            if not _filter_by_type():
-                continue
-            #if not _filter_by_attrs():
-            #    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:
-            return find_results
+        :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()
+        self._cfgimpl_meta = None
 
+    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+        if 'values' in only:
+            self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
+        if 'settings' in only:
+            self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
 
-class MetaConfig(object):
-    __slots__ = ('_children')
 
-    def __init__(self, children):
+class MetaConfig(ConfigCommon):
+    __slots__ = ('_cfgimpl_children',)
+
+    def __init__(self, children, meta=True):
         if not isinstance(children, list):
             raise ValueError(_("metaconfig's children must be a list"))
-        descr = None
-        for child in children:
-            if not isinstance(child, Config):
-                raise ValueError(_("metaconfig's children must be Config, not {0}"
-                                   "".format(type(Config))))
-            if descr is None:
-                descr = child.cfgimpl_get_description()
-            elif descr is child.cfgimpl_get_description():
-                raise ValueError(_('all config in MetaConfig must have same '
-                                   'optiondescription'))
-
-        self._children = children
-
-    def _find(self, bytype, byname, byvalue, first, type_):
-        if type_ not in ('option', 'path'):
-            raise ValueError(_('unknown type_ type {0} for _find'
-                               '').format(type_))
-        #all children have same optiondescription, search in first one's
-        return self._children[0]._find(bytype, byname, byvalue, first=first,
-                                       type_=type_, check_properties=False)
+        self._cfgimpl_descr = None
+        if meta:
+            for child in children:
+                if not isinstance(child, ConfigCommon):
+                    raise ValueError(_("metaconfig's children must be Config, not {0}"
+                                       "".format(type(child))))
+                if self._cfgimpl_descr is None:
+                    self._cfgimpl_descr = child.cfgimpl_get_description()
+                elif not self._cfgimpl_descr is child.cfgimpl_get_description():
+                    raise ValueError(_('all config in MetaConfig must have same '
+                                       'optiondescription'))
+                if child.cfgimpl_get_meta() is not None:
+                    raise ValueError(_("child has already a metaconfig's"))
+                child._cfgimpl_meta = self
+
+        self._cfgimpl_children = children
+        self._cfgimpl_settings = Setting(self)
+        self._cfgimpl_values = Values(self)
+        self._cfgimpl_meta = None
 
-    def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
-        return self._find(bytype, byname, byvalue, type_, first=False)
+    def cfgimpl_get_context(self):
+        return self
 
-    def find_first(self, bytype=None, byname=None, byvalue=None,
-                   type_='option'):
-        return self._find(bytype, byname, byvalue, type_, first=True)
+    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+        if 'values' in only:
+            self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
+        if 'settings' in only:
+            self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
+        for child in self._cfgimpl_children:
+            child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
 
-    def __setattr__(self, name, value):
-        for child in self._children:
+    def set_contexts(self, path, value):
+        for child in self._cfgimpl_children:
             try:
-                setattr(child, name, value)
+                if not isinstance(child, MetaConfig):
+                    setattr(child, path, value)
+                else:
+                    child.set_contexts(path, value)
             except PropertiesOptionError:
                 pass
 
+    def find_first_contexts(self, byname=None, bypath=None, byvalue=None, type_='context'):
+        ret = []
+        try:
+            if bypath is None and byname is not None and \
+                    self.cfgimpl_get_description() is not None:
+                bypath = self._find(bytype=None, byvalue=None, byname=byname,
+                                    first=True, type_='path',
+                                    check_properties=False)
+        except ConfigError:
+            pass
+        for child in self._cfgimpl_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_))
+                else:
+                    ret.extend(child.find_first_contexts(byname=byname,
+                                                         bypath=bypath,
+                                                         byvalue=byvalue,
+                                                         type_=type_))
+            except AttributeError:
+                pass
+        return ret
+
 
 def mandatory_warnings(config):
     """convenience function to trace Options that are mandatory and
index 95c5b7c..7f58f6d 100644 (file)
@@ -40,6 +40,7 @@ class PropertiesOptionError(AttributeError):
 #____________config___________
 class ConfigError(StandardError):
     """try to change owner for an option without value
+    or if _cfgimpl_descr is None
     or if error in calculation"""
     pass
 
index e973489..005bfa6 100644 (file)
@@ -31,9 +31,9 @@ from tiramisu.i18n import _
 from tiramisu.autolib import carry_out_calculation
 
 name_regexp = re.compile(r'^\d+')
-forbidden_names = ['iter_all', 'iter_group', 'find', 'find_fisrt',
+forbidden_names = ['iter_all', 'iter_group', 'find', 'find_first',
                    'make_dict', 'unwrap_from_path', 'read_only',
-                   'read_write', 'getowner']
+                   'read_write', 'getowner', 'set_contexts']
 
 
 def valid_name(name):
@@ -43,8 +43,8 @@ def valid_name(name):
         return False
     if re.match(name_regexp, name) is None and not name.startswith('_') \
             and name not in forbidden_names \
-            and not name.startswith('optimpl_') and \
-            not name.startswith('cfgimpl_'):
+            and not name.startswith('optimpl_') \
+            and not name.startswith('cfgimpl_'):
         return True
     else:
         return False
index 9190020..c69b588 100644 (file)
@@ -39,11 +39,18 @@ class Values(object):
         self._cache = {}
         super(Values, self).__init__()
 
+    def _get_default(self, opt):
+        meta = self.context.cfgimpl_get_meta()
+        if meta is not None:
+            return meta.cfgimpl_get_values()[opt]
+        else:
+            return opt.optimpl_getdefault()
+
     def _get_value(self, opt):
         "return value or default value if not set"
         #if no value
         if opt not in self._values:
-            value = opt.optimpl_getdefault()
+            value = self._get_default(opt)
             if opt.optimpl_is_multi():
                 value = Multi(value, self.context, opt)
         else:
@@ -114,7 +121,7 @@ class Values(object):
                 self._reset(opt)
         # frozen and force default
         elif is_frozen and 'force_default_on_freeze' in setting[opt]:
-            value = opt.optimpl_getdefault()
+            value = self._get_default(opt)
             if opt.optimpl_is_multi():
                 value = Multi(value, self.context, opt)
         if validate and not opt.optimpl_validate(value, self.context, 'validator' in setting):
@@ -154,7 +161,11 @@ class Values(object):
         self._values[opt] = (setting.getowner(), value)
 
     def getowner(self, opt):
-        return self._values.get(opt, (owners.default, None))[0]
+        owner = self._values.get(opt, (owners.default, None))[0]
+        meta = self.context.cfgimpl_get_meta()
+        if owner is owners.default and meta is not None:
+            owner = meta.cfgimpl_get_values().getowner(opt)
+        return owner
 
     def setowner(self, opt, owner):
         if opt not in self._values: