diff --git a/test/test_dereference.py b/test/test_dereference.py index be8dfde..358c1b9 100644 --- a/test/test_dereference.py +++ b/test/test_dereference.py @@ -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 diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py index 9214cb4..6986edc 100644 --- a/test/test_metaconfig.py +++ b/test/test_metaconfig.py @@ -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 diff --git a/test/test_state.py b/test/test_state.py index ef46ce2..3188d3b 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -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 diff --git a/tiramisu/config.py b/tiramisu/config.py index 79527e8..d6399a2 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -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