serialize metaconfig/groupconfig
authorEmmanuel Garette <egarette@cadoles.com>
Mon, 30 Sep 2013 14:22:08 +0000 (16:22 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Mon, 30 Sep 2013 14:22:08 +0000 (16:22 +0200)
test/test_dereference.py
test/test_metaconfig.py
test/test_state.py
tiramisu/config.py

index be8dfde..358c1b9 100644 (file)
@@ -2,8 +2,8 @@
 import autopath
 #from py.test import raises
 
-from tiramisu.config import Config
-from tiramisu.option import BoolOption, OptionDescription
+from tiramisu.config import Config, GroupConfig, MetaConfig
+from tiramisu.option import BoolOption, IntOption, OptionDescription
 import weakref
 
 
@@ -109,3 +109,31 @@ def test_deref_optiondescription_config():
     assert w() is not None
     del(c)
     assert w() is None
+
+
+def test_deref_groupconfig():
+    i1 = IntOption('i1', '')
+    od1 = OptionDescription('od1', '', [i1])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2)
+    conf2 = Config(od2)
+    meta = GroupConfig([conf1, conf2])
+    w = weakref.ref(conf1)
+    del(conf1)
+    assert w() is not None
+    del(meta)
+    assert w() is None
+
+
+def test_deref_metaconfig():
+    i1 = IntOption('i1', '')
+    od1 = OptionDescription('od1', '', [i1])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2)
+    conf2 = Config(od2)
+    meta = MetaConfig([conf1, conf2])
+    w = weakref.ref(conf1)
+    del(conf1)
+    assert w() is not None
+    del(meta)
+    assert w() is None
index 9214cb4..6986edc 100644 (file)
@@ -3,7 +3,7 @@ import autopath
 from py.test import raises
 
 from tiramisu.setting import owners
-from tiramisu.config import Config, MetaConfig
+from tiramisu.config import Config, GroupConfig, MetaConfig
 from tiramisu.option import IntOption, OptionDescription
 from tiramisu.error import ConfigError
 
@@ -26,6 +26,7 @@ def make_description():
 
 #FIXME ne pas mettre 2 meta dans une config
 #FIXME ne pas mettre 2 OD differents dans un meta
+#FIXME serialization
 def test_none():
     meta = make_description()
     conf1, conf2 = meta._impl_children
@@ -89,7 +90,7 @@ def test_contexts():
     conf1, conf2 = meta._impl_children
     assert conf1.od1.i2 == conf2.od1.i2 == 1
     assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default
-    meta.set_contexts('od1.i2', 6)
+    meta.setattrs('od1.i2', 6)
     assert meta.od1.i2 == 1
     assert conf1.od1.i2 == conf2.od1.i2 == 6
     assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.user
@@ -142,14 +143,14 @@ def test_meta_meta_set():
     meta2 = MetaConfig([meta1])
     meta2.cfgimpl_get_settings().setowner(owners.meta)
     conf1, conf2 = meta1._impl_children
-    meta2.set_contexts('od1.i1', 7)
+    meta2.setattrs('od1.i1', 7)
     assert conf1.od1.i1 == conf2.od1.i1 == 7
     assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user
-    assert [conf1, conf2] == meta2.find_first_contexts(byname='i1', byvalue=7)
+    assert [conf1, conf2] == meta2.find_firsts(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)
-    raises(AttributeError, "meta2.find_first_contexts(byname='i1', byvalue=10)")
+    assert [conf2] == meta2.find_firsts(byname='i1', byvalue=7)
+    assert [conf1] == meta2.find_firsts(byname='i1', byvalue=8)
+    raises(AttributeError, "meta2.find_firsts(byname='i1', byvalue=10)")
 
 
 def test_not_meta():
@@ -158,10 +159,10 @@ def test_not_meta():
     od2 = OptionDescription('od2', '', [od1])
     conf1 = Config(od2)
     conf2 = Config(od2)
-    meta = MetaConfig([conf1, conf2], False)
+    meta = GroupConfig([conf1, conf2])
     raises(ConfigError, 'meta.od1.i1')
     conf1, conf2 = meta._impl_children
-    meta.set_contexts('od1.i1', 7)
+    meta.setattrs('od1.i1', 7)
     assert conf1.od1.i1 == conf2.od1.i1 == 7
     assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user
 
index ef46ce2..3188d3b 100644 (file)
@@ -1,6 +1,6 @@
 from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \
-    OptionDescription
-from tiramisu.config import Config
+    IntOption, OptionDescription
+from tiramisu.config import Config, GroupConfig, MetaConfig
 from tiramisu.setting import owners
 from tiramisu.storage import delete_session
 from tiramisu.error import ConfigError
@@ -90,6 +90,45 @@ def _diff_opt(opt1, opt2):
             assert val1 == val2
 
 
+def _diff_conf(cfg1, cfg2):
+    attr1 = set(_get_slots(cfg1))
+    attr2 = set(_get_slots(cfg2))
+    diff1 = attr1 - attr2
+    diff2 = attr2 - attr1
+    if diff1 != set():
+        raise Exception('more attribute in cfg1 {0}'.format(list(diff1)))
+    if diff2 != set():
+        raise Exception('more attribute in cfg2 {0}'.format(list(diff2)))
+    for attr in attr1:
+        if attr in ('_impl_context', '__weakref__'):
+            continue
+        err1 = False
+        err2 = False
+        val1 = None
+        val2 = None
+        try:
+            val1 = getattr(cfg1, attr)
+        except:
+            err1 = True
+
+        try:
+            val2 = getattr(cfg2, attr)
+        except:
+            err2 = True
+        assert err1 == err2
+        if val1 is None:
+            assert val1 == val2
+        elif attr == '_impl_values':
+            assert cfg1.cfgimpl_get_values().get_modified_values() == cfg2.cfgimpl_get_values().get_modified_values()
+        elif attr == '_impl_settings':
+            assert cfg1.cfgimpl_get_settings().get_modified_properties() == cfg2.cfgimpl_get_settings().get_modified_properties()
+            assert cfg1.cfgimpl_get_settings().get_modified_permissives() == cfg2.cfgimpl_get_settings().get_modified_permissives()
+        elif attr == '_impl_descr':
+            _diff_opt(cfg1.cfgimpl_get_description(), cfg2.cfgimpl_get_description())
+        else:
+            assert val1 == val2
+
+
 def test_diff_opt():
     b = BoolOption('b', '')
     u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}])
@@ -169,10 +208,7 @@ def test_state_config():
         cfg._impl_test = True
     a = dumps(cfg)
     q = loads(a)
-    _diff_opt(maconfig, q.cfgimpl_get_description())
-    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
-    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
-    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    _diff_conf(cfg, q)
     try:
         delete_session('29090931')
     except ConfigError:
@@ -191,12 +227,9 @@ def test_state_properties():
     cfg.cfgimpl_get_settings()[val1].append('test')
     a = dumps(cfg)
     q = loads(a)
-    _diff_opt(maconfig, q.cfgimpl_get_description())
-    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
-    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
-    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    _diff_conf(cfg, q)
     try:
-        delete_session('29090931')
+        delete_session('29090932')
     except ConfigError:
         pass
 
@@ -212,15 +245,12 @@ def test_state_values():
     cfg.val1 = True
     a = dumps(cfg)
     q = loads(a)
-    _diff_opt(maconfig, q.cfgimpl_get_description())
-    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
-    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
-    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    _diff_conf(cfg, q)
     q.val1 = False
     #assert cfg.val1 is True
     assert q.val1 is False
     try:
-        delete_session('29090931')
+        delete_session('29090933')
     except ConfigError:
         pass
 
@@ -238,14 +268,53 @@ def test_state_values_owner():
     cfg.val1 = True
     a = dumps(cfg)
     q = loads(a)
-    _diff_opt(maconfig, q.cfgimpl_get_description())
-    assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
-    assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
-    assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
+    _diff_conf(cfg, q)
     q.val1 = False
     nval1 = q.cfgimpl_get_description().val1
     assert q.getowner(nval1) == owners.newowner
     try:
-        delete_session('29090931')
+        delete_session('29090934')
+    except ConfigError:
+        pass
+
+
+def test_state_metaconfig():
+    i1 = IntOption('i1', '')
+    od1 = OptionDescription('od1', '', [i1])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2, session_id='29090935')
+    conf1._impl_test = True
+    conf2 = Config(od2, session_id='29090936')
+    conf2._impl_test = True
+    meta = MetaConfig([conf1, conf2], session_id='29090937')
+    meta._impl_test = True
+    a = dumps(meta)
+    q = loads(a)
+    _diff_conf(meta, q)
+    try:
+        delete_session('29090935')
+        delete_session('29090936')
+        delete_session('29090937')
+    except ConfigError:
+        pass
+
+
+def test_state_groupconfig():
+    i1 = IntOption('i1', '')
+    od1 = OptionDescription('od1', '', [i1])
+    od2 = OptionDescription('od2', '', [od1])
+    conf1 = Config(od2, session_id='29090935')
+    conf1._impl_test = True
+    conf2 = Config(od2, session_id='29090936')
+    conf2._impl_test = True
+    meta = GroupConfig([conf1, conf2], session_id='29090937')
+    meta._impl_test = True
+    a = dumps(meta)
+    q = loads(a)
+    _diff_conf(meta, q)
+    try:
+        delete_session('29090935')
+        delete_session('29090936')
+        delete_session('29090937')
     except ConfigError:
         pass
index 79527e8..d6399a2 100644 (file)
@@ -161,7 +161,7 @@ class SubConfig(object):
     def cfgimpl_get_description(self):
         if self._impl_descr is None:
             raise ConfigError(_('no option description found for this config'
-                                ' (may be metaconfig without meta)'))
+                                ' (may be GroupConfig)'))
         else:
             return self._impl_descr
 
@@ -467,9 +467,9 @@ class SubConfig(object):
         return context_descr.impl_get_path_by_opt(descr)
 
 
-class CommonConfig(SubConfig):
-    "abstract base class for the Config and the MetaConfig"
-    __slots__ = ('_impl_values', '_impl_settings', '_impl_meta')
+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()
@@ -508,7 +508,8 @@ class CommonConfig(SubConfig):
         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):
@@ -526,37 +527,12 @@ class CommonConfig(SubConfig):
         """
         return self._impl_values.get_information(key, default)
 
-
-# ____________________________________________________________
-class Config(CommonConfig):
-    "main configuration management entry"
-    __slots__ = ('__weakref__', '_impl_test')
-
-    def __init__(self, descr, session_id=None, persistent=False):
-        """ Configuration option management master class
-
-        :param descr: describes the configuration schema
-        :type descr: an instance of ``option.OptionDescription``
-        :param context: the current root config
-        :type context: `Config`
-        :param session_id: session ID is import with persistent Config to
-        retrieve good session
-        :type session_id: `str`
-        :param persistent: if persistent, don't delete storage when leaving
-        :type persistent: `boolean`
-        """
-        settings, values = get_storages(self, session_id, persistent)
-        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_meta = None
-        #undocumented option used only in test script
-        self._impl_test = False
-
+    # ----- state
     def __getstate__(self):
         if self._impl_meta is not None:
-            raise ConfigError('cannot serialize Config with meta')
+            #FIXME _impl_meta est un weakref => faut pas sauvegarder mais faut bien savoir si c'est un méta ou pas au final ...
+            #en fait il faut ne pouvoir sérialisé que depuis une MetaConfig ... et pas directement comme pour les options
+            raise ConfigError('cannot serialize Config with MetaConfig')
         slots = set()
         for subclass in self.__class__.__mro__:
             if subclass is not object:
@@ -589,6 +565,34 @@ class Config(CommonConfig):
         self._impl_values._impl_setstate(storage)
         self._impl_settings._impl_setstate(storage)
 
+
+# ____________________________________________________________
+class Config(_CommonConfig):
+    "main configuration management entry"
+    __slots__ = ('__weakref__',)
+
+    def __init__(self, descr, session_id=None, persistent=False):
+        """ Configuration option management master class
+
+        :param descr: describes the configuration schema
+        :type descr: an instance of ``option.OptionDescription``
+        :param context: the current root config
+        :type context: `Config`
+        :param session_id: session ID is import with persistent Config to
+        retrieve good session
+        :type session_id: `str`
+        :param persistent: if persistent, don't delete storage when leaving
+        :type persistent: `boolean`
+        """
+        settings, values = get_storages(self, session_id, persistent)
+        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_meta = None
+        #undocumented option used only in test script
+        self._impl_test = False
+
     def cfgimpl_reset_cache(self,
                             only_expired=False,
                             only=('values', 'settings')):
@@ -598,42 +602,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')):
@@ -644,38 +635,49 @@ 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=None, byvalue=None,
+                    type_='path', display_error=True):
+        """Find first not in current GroupConfig, but in each children
+        """
         ret = []
+        #if MetaConfig, all children have same OptionDescription as context
+        #so search only one time for all children
         try:
             if bypath is None and byname is not None and \
-                    self.cfgimpl_get_description() is not None:
+                    isinstance(self, MetaConfig):
                 bypath = self._find(bytype=None, byvalue=None, byname=byname,
                                     first=True, type_='path',
                                     check_properties=False,
                                     display_error=display_error)
-        except ConfigError:
+                byname = None
+        except AttributeError:
             pass
         for child in self._impl_children:
             try:
                 if not isinstance(child, MetaConfig):
                     if bypath is not None:
+                        #if byvalue is None, try if not raise
+                        value = getattr(child, bypath)
                         if byvalue is not None:
-                            if getattr(child, bypath) == byvalue:
-                                ret.append(child)
+                            if isinstance(value, Multi):
+                                if byvalue in value:
+                                    ret.append(child)
+                            else:
+                                if value == byvalue:
+                                    ret.append(child)
                         else:
-                            #not raise
-                            getattr(child, bypath)
                             ret.append(child)
                     else:
                         ret.append(child.find_first(byname=byname,
@@ -683,16 +685,38 @@ class MetaConfig(CommonConfig):
                                                     type_=type_,
                                                     display_error=False))
                 else:
-                    ret.extend(child.find_first_contexts(byname=byname,
-                                                         bypath=bypath,
-                                                         byvalue=byvalue,
-                                                         type_=type_,
-                                                         display_error=False))
+                    ret.extend(child.find_firsts(byname=byname,
+                                                 bypath=bypath,
+                                                 byvalue=byvalue,
+                                                 type_=type_,
+                                                 display_error=False))
             except AttributeError:
                 pass
         return self._find_return_results(ret, display_error)
 
 
+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)
+
+
 def mandatory_warnings(config):
     """convenience function to trace Options that are mandatory and
     where no value has been set