From 7165f732921ff6128ad2de1c14fc11ae1ac6349c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 17 Sep 2013 09:02:10 +0200 Subject: [PATCH 01/15] add metaconfig --- test/test_metaconfig.py | 302 ++++++++++++++++++++-------------------- tiramisu/config.py | 174 +++++++++++------------ 2 files changed, 238 insertions(+), 238 deletions(-) diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py index 35d65d5..9214cb4 100644 --- a/test/test_metaconfig.py +++ b/test/test_metaconfig.py @@ -1,172 +1,172 @@ -#import autopath +import autopath -#from py.test import raises +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 +from tiramisu.setting import owners +from tiramisu.config import Config, MetaConfig +from tiramisu.option import IntOption, OptionDescription +from tiramisu.error import ConfigError -#owners.addowner('meta') +owners.addowner('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 +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._impl_children -# assert conf1.od1.i3 is conf2.od1.i3 is None -# assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default -# meta.od1.i3 = 3 -# assert conf1.od1.i3 == conf2.od1.i3 == 3 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i3')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.meta -# meta.od1.i3 = 4 -# assert conf1.od1.i3 == 2 -# assert conf2.od1.i3 == 4 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.meta -# del(meta.od1.i3) -# assert conf1.od1.i3 == 2 -# assert conf2.od1.i3 is None -# assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default -# del(conf1.od1.i3) -# assert conf1.od1.i3 is conf2.od1.i3 is None -# assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default +#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._impl_children + assert conf1.od1.i3 is conf2.od1.i3 is None + assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default + meta.od1.i3 = 3 + assert conf1.od1.i3 == conf2.od1.i3 == 3 + assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i3')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.meta + meta.od1.i3 = 4 + assert conf1.od1.i3 == 2 + assert conf2.od1.i3 == 4 + assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.meta + del(meta.od1.i3) + assert conf1.od1.i3 == 2 + assert conf2.od1.i3 is None + assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default + del(conf1.od1.i3) + assert conf1.od1.i3 is conf2.od1.i3 is None + assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default -#def test_default(): -# meta = make_description() -# 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.od1.i2 = 3 -# assert conf1.od1.i2 == conf2.od1.i2 == 3 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta -# meta.od1.i2 = 4 -# assert conf1.od1.i2 == 2 -# assert conf2.od1.i2 == 4 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta -# del(meta.od1.i2) -# assert conf1.od1.i2 == 2 -# assert conf2.od1.i2 == 1 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default -# del(conf1.od1.i2) -# 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 +def test_default(): + meta = make_description() + 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.od1.i2 = 3 + assert conf1.od1.i2 == conf2.od1.i2 == 3 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta + meta.od1.i2 = 4 + assert conf1.od1.i2 == 2 + assert conf2.od1.i2 == 4 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta + del(meta.od1.i2) + assert conf1.od1.i2 == 2 + assert conf2.od1.i2 == 1 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default + del(conf1.od1.i2) + 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 -#def test_contexts(): -# meta = make_description() -# 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) -# 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 +def test_contexts(): + meta = make_description() + 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) + 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 -#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_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._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 -# meta2.od1.i2 = 3 -# assert conf1.od1.i2 == conf2.od1.i2 == 3 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta -# meta2.od1.i2 = 4 -# assert conf1.od1.i2 == 2 -# assert conf2.od1.i2 == 4 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta -# del(meta2.od1.i2) -# assert conf1.od1.i2 == 2 -# assert conf2.od1.i2 == 1 -# assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user -# assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default -# del(conf1.od1.i2) -# 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 -# meta1.od1.i2 = 6 -# 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.meta +def test_meta_meta(): + meta1 = make_description() + meta2 = MetaConfig([meta1]) + meta2.cfgimpl_get_settings().setowner(owners.meta) + conf1, conf2 = meta1._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 + meta2.od1.i2 = 3 + assert conf1.od1.i2 == conf2.od1.i2 == 3 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('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(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta + meta2.od1.i2 = 4 + assert conf1.od1.i2 == 2 + assert conf2.od1.i2 == 4 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.meta + del(meta2.od1.i2) + assert conf1.od1.i2 == 2 + assert conf2.od1.i2 == 1 + assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is owners.user + assert conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default + del(conf1.od1.i2) + 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 + meta1.od1.i2 = 6 + 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.meta -#def test_meta_meta_set(): -# meta1 = make_description() -# meta2 = MetaConfig([meta1]) -# meta2.cfgimpl_get_settings().setowner(owners.meta) -# conf1, conf2 = meta1._impl_children -# meta2.set_contexts('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) -# 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)") +def test_meta_meta_set(): + meta1 = make_description() + meta2 = MetaConfig([meta1]) + meta2.cfgimpl_get_settings().setowner(owners.meta) + conf1, conf2 = meta1._impl_children + meta2.set_contexts('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) + 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)") -#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._impl_children -# meta.set_contexts('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 +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._impl_children + meta.set_contexts('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 -#def test_meta_path(): -# meta = make_description() -# assert meta._impl_path is None -# assert meta.od1._impl_path == 'od1' +def test_meta_path(): + meta = make_description() + assert meta._impl_path is None + assert meta.od1._impl_path == 'od1' diff --git a/tiramisu/config.py b/tiramisu/config.py index fac3caf..591eb68 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -43,7 +43,7 @@ class SubConfig(object): :type subpath: `str` with the path name """ # main option description - if not isinstance(descr, OptionDescription): + if descr is not None and not isinstance(descr, OptionDescription): raise TypeError(_('descr must be an optiondescription, not {0}' ).format(type(descr))) self._impl_descr = descr @@ -552,99 +552,99 @@ class Config(CommonConfig): self.cfgimpl_get_settings().reset_cache(only_expired=only_expired) -#class MetaConfig(CommonConfig): -# __slots__ = ('_impl_children',) +class MetaConfig(CommonConfig): + __slots__ = ('_impl_children', '__weakref__') -# def __init__(self, children, meta=True, session_id=None, persistent=False): -# if not isinstance(children, list): -# raise ValueError(_("metaconfig's children must be a list")) -# self._impl_descr = None -# self._impl_path = 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 self._impl_descr is None: -# self._impl_descr = child.cfgimpl_get_description() -# elif not self._impl_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 + def __init__(self, children, meta=True, session_id=None, persistent=False): + 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) -# self._impl_meta = None + 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)) + self._impl_meta = None -# def cfgimpl_get_children(self): -# return self._impl_children + 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 wich has a setting, that is itself" + return self -# 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._impl_children: -# child.cfgimpl_reset_cache(only_expired=only_expired, only=only) + 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._impl_children: + child.cfgimpl_reset_cache(only_expired=only_expired, only=only) -# def set_contexts(self, path, value): -# for child in self._impl_children: -# try: -# if not isinstance(child, MetaConfig): -# setattr(child, path, value) -# else: -# child.set_contexts(path, value) -# except PropertiesOptionError: -# pass + def set_contexts(self, path, value): + for child in self._impl_children: + try: + 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_='path', display_error=True): -# 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, -# display_error=display_error) -# except ConfigError: -# pass -# 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)) -# else: -# ret.extend(child.find_first_contexts(byname=byname, -# bypath=bypath, -# byvalue=byvalue, -# type_=type_, -# display_error=False)) -# except AttributeError: -# pass -# return self._find_return_results(ret, display_error) + def find_first_contexts(self, byname=None, bypath=None, byvalue=None, + type_='path', display_error=True): + 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, + display_error=display_error) + except ConfigError: + pass + 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)) + else: + ret.extend(child.find_first_contexts(byname=byname, + bypath=bypath, + byvalue=byvalue, + type_=type_, + display_error=False)) + except AttributeError: + pass + return self._find_return_results(ret, display_error) def mandatory_warnings(config): From feeb9842f5dd554913690b763aec0fd306fa8446 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Mon, 30 Sep 2013 16:22:08 +0200 Subject: [PATCH 02/15] serialize metaconfig/groupconfig --- test/test_dereference.py | 32 +++++++- test/test_metaconfig.py | 19 ++--- test/test_state.py | 111 ++++++++++++++++++++----- tiramisu/config.py | 170 ++++++++++++++++++++++----------------- 4 files changed, 227 insertions(+), 105 deletions(-) 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 From b606d23801f04d87c7e6eb3293579b457574e036 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 15 Oct 2013 18:23:36 +0200 Subject: [PATCH 03/15] add imp_meta --- tiramisu/config.py | 3 ++- translations/fr/tiramisu.po | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tiramisu/config.py b/tiramisu/config.py index d6399a2..d024159 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -537,7 +537,7 @@ class _CommonConfig(SubConfig): for subclass in self.__class__.__mro__: if subclass is not object: slots.update(subclass.__slots__) - slots -= frozenset(['_impl_context', '__weakref__']) + slots -= frozenset(['_impl_context', '_impl_meta', '__weakref__']) state = {} for slot in slots: try: @@ -564,6 +564,7 @@ class _CommonConfig(SubConfig): storage = get_storage(test=self._impl_test, **state['_storage']) self._impl_values._impl_setstate(storage) self._impl_settings._impl_setstate(storage) + self._impl_meta = None # ____________________________________________________________ diff --git a/translations/fr/tiramisu.po b/translations/fr/tiramisu.po index 256a0da..93e30f4 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -79,7 +79,7 @@ msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seule" #: tiramisu/option.py:142 tiramisu/value.py:360 msgid "information's item not found: {0}" -msgstr "aucune config spécifié alors que c'est nécessaire" +msgstr "aucune config spécifiée alors que c'est nécessaire" #: tiramisu/option.py:204 msgid "cannot serialize Option, only in OptionDescription" From f48f3e754402169136b4beedf80c745f587226c9 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 1 Feb 2014 16:49:16 +0100 Subject: [PATCH 04/15] add tests for find() --- test/test_config_api.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/test/test_config_api.py b/test/test_config_api.py index f0681ea..7489766 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -13,17 +13,20 @@ def make_description(): objspaceoption = ChoiceOption('objspace', 'Object space', ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) + booloption2 = BoolOption('bool', 'Test boolean option', default=True) intoption = IntOption('int', 'Test int option', default=0) + floatoption2 = FloatOption('float', 'Test float option', default=2.3) floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Tests', default=False) wantframework_option = BoolOption('wantframework', 'Test', default=False) - gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) + gcgroup2 = OptionDescription('gc2', '', [booloption2]) + gcgroup = OptionDescription('gc', '', [gcgroup2, gcoption, gcdummy, floatoption]) descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption, wantref_option, stroption, wantframework_option, - intoption, boolop]) + intoption, boolop, floatoption2]) return descr @@ -101,7 +104,10 @@ def test_find_in_config(): descr = make_description() conf = Config(descr) assert conf.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')] + assert conf.find(byname='float') == [conf.unwrap_from_path('gc.float'), conf.unwrap_from_path('float')] + assert conf.find_first(byname='bool') == conf.unwrap_from_path('gc.gc2.bool') assert conf.find_first(byname='dummy') == conf.unwrap_from_path('gc.dummy') + assert conf.find_first(byname='float') == conf.unwrap_from_path('gc.float') assert conf.find(bytype=ChoiceOption) == [conf.unwrap_from_path('gc.name'), conf.unwrap_from_path('objspace')] assert conf.find_first(bytype=ChoiceOption) == conf.unwrap_from_path('gc.name') assert conf.find(byvalue='ref') == [conf.unwrap_from_path('gc.name')] @@ -111,9 +117,14 @@ def test_find_in_config(): assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy') assert conf.find(byvalue=False, byname='dummy') == [conf.unwrap_from_path('gc.dummy')] assert conf.find_first(byvalue=False, byname='dummy') == conf.unwrap_from_path('gc.dummy') - ## byattrs - #assert conf.find_first(byattrs= dict(default=2.3)) == conf.unwrap_from_path('gc.float') - #assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy') + #subconfig + assert conf.gc.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')] + assert conf.gc.find(byname='float') == [conf.unwrap_from_path('gc.float')] + assert conf.gc.find(byname='bool') == [conf.unwrap_from_path('gc.gc2.bool')] + raises(AttributeError, "conf.gc.find(byname='wantref').first()") + # not OptionDescription + raises(AttributeError, "conf.find_first(byname='gc')") + raises(AttributeError, "conf.gc.find_first(byname='gc2')") def test_find_multi(): From 71b235551eca7335c5f9a1fc5e5dc5efd8472a4e Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 1 Feb 2014 17:25:31 +0100 Subject: [PATCH 05/15] add tests for find() --- test/test_config_api.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/test_config_api.py b/test/test_config_api.py index 7489766..db40c57 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -10,10 +10,12 @@ from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ def make_description(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) + prop = BoolOption('prop', '', properties=('disabled',)) + prop2 = BoolOption('prop', '', properties=('hidden',)) objspaceoption = ChoiceOption('objspace', 'Object space', ('std', 'thunk'), 'std') booloption = BoolOption('bool', 'Test boolean option', default=True) - booloption2 = BoolOption('bool', 'Test boolean option', default=True) + booloption2 = BoolOption('bool', 'Test boolean option', default=False) intoption = IntOption('int', 'Test int option', default=0) floatoption2 = FloatOption('float', 'Test float option', default=2.3) floatoption = FloatOption('float', 'Test float option', default=2.3) @@ -21,8 +23,8 @@ def make_description(): boolop = BoolOption('boolop', 'Test boolean option op', default=True) wantref_option = BoolOption('wantref', 'Tests', default=False) wantframework_option = BoolOption('wantframework', 'Test', default=False) - gcgroup2 = OptionDescription('gc2', '', [booloption2]) - gcgroup = OptionDescription('gc', '', [gcgroup2, gcoption, gcdummy, floatoption]) + gcgroup2 = OptionDescription('gc2', '', [booloption2, prop]) + gcgroup = OptionDescription('gc', '', [gcgroup2, gcoption, gcdummy, floatoption, prop2]) descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption, wantref_option, stroption, wantframework_option, @@ -103,15 +105,22 @@ def test_find_in_config(): "finds option in config" descr = make_description() conf = Config(descr) + conf.read_only() assert conf.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')] assert conf.find(byname='float') == [conf.unwrap_from_path('gc.float'), conf.unwrap_from_path('float')] assert conf.find_first(byname='bool') == conf.unwrap_from_path('gc.gc2.bool') + assert conf.find_first(byname='bool', byvalue=True) == conf.unwrap_from_path('bool') assert conf.find_first(byname='dummy') == conf.unwrap_from_path('gc.dummy') assert conf.find_first(byname='float') == conf.unwrap_from_path('gc.float') assert conf.find(bytype=ChoiceOption) == [conf.unwrap_from_path('gc.name'), conf.unwrap_from_path('objspace')] assert conf.find_first(bytype=ChoiceOption) == conf.unwrap_from_path('gc.name') assert conf.find(byvalue='ref') == [conf.unwrap_from_path('gc.name')] assert conf.find_first(byvalue='ref') == conf.unwrap_from_path('gc.name') + assert conf.find(byname='prop') == [conf.unwrap_from_path('gc.prop')] + conf.read_write() + raises(AttributeError, "assert conf.find(byname='prop')") + assert conf.find(byname='prop', check_properties=False) == [conf.unwrap_from_path('gc.gc2.prop'), conf.unwrap_from_path('gc.prop')] + #assert conf.find_first(byname='prop') == conf.unwrap_from_path('gc.prop') # combinaison of filters assert conf.find(bytype=BoolOption, byname='dummy') == [conf.unwrap_from_path('gc.dummy')] assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy') @@ -121,7 +130,12 @@ def test_find_in_config(): assert conf.gc.find(byname='dummy') == [conf.unwrap_from_path('gc.dummy')] assert conf.gc.find(byname='float') == [conf.unwrap_from_path('gc.float')] assert conf.gc.find(byname='bool') == [conf.unwrap_from_path('gc.gc2.bool')] + assert conf.gc.find_first(byname='bool', byvalue=False) == conf.unwrap_from_path('gc.gc2.bool') + raises(AttributeError, "assert conf.gc.find_first(byname='bool', byvalue=True)") raises(AttributeError, "conf.gc.find(byname='wantref').first()") + assert conf.gc.find(byname='prop', check_properties=False) == [conf.unwrap_from_path('gc.gc2.prop'), conf.unwrap_from_path('gc.prop')] + conf.read_only() + assert conf.gc.find(byname='prop') == [conf.unwrap_from_path('gc.prop')] # not OptionDescription raises(AttributeError, "conf.find_first(byname='gc')") raises(AttributeError, "conf.gc.find_first(byname='gc2')") From 683e40fbb5143190932edbf0fe03a7ee3464b341 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 2 Feb 2014 18:20:01 +0100 Subject: [PATCH 06/15] when change len of calculated master, change len of slave too --- test/test_option_calculation.py | 63 ++++++++++++++++++++++ test/test_state.py | 43 +++++++++++++++ tiramisu/autolib.py | 5 +- tiramisu/value.py | 95 ++++++++++++++++----------------- 4 files changed, 157 insertions(+), 49 deletions(-) diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 91d61a4..89f9291 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -373,6 +373,9 @@ def test_callback_multi_value(): assert cfg.val2 == ['val'] assert cfg.val3 == ['yes'] assert cfg.val4 == ['val', 'yes'] + cfg.val2.append('new') + assert cfg.val1 == ['val'] + assert cfg.val2 == ['val', 'new'] def test_callback_multi_list(): @@ -466,6 +469,66 @@ def test_callback_master_and_slaves_slave(): assert cfg.val1.val2 == ['val2', 'val2', 'val'] +def test_callback_master_and_slaves_slave_cal(): + val3 = StrOption('val3', "", multi=True) + val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val3, False),)}) + val2 = StrOption('val2', "", multi=True, callback=return_val) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val3]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val3 == [] + assert cfg.val1.val1 == [] + assert cfg.val1.val2 == [] + cfg.val1.val1 = ['val1'] + cfg.val3 = ['val1'] + assert cfg.val1.val1 == ['val1'] + assert cfg.val1.val2 == ['val'] + assert cfg.val1.val1 == ['val1'] + assert cfg.val1.val2 == ['val'] + del(cfg.val1.val1) + cfg.val1.val2 = ['val'] + cfg.val3 = ['val1', 'val2'] + assert cfg.val1.val2 == ['val', 'val'] + assert cfg.val1.val1 == ['val1', 'val2'] + cfg.val1.val2 = ['val1', 'val2'] + cfg.val3.pop(1) + # cannot remove slave's value because master is calculated + # so raise + raises(SlaveError, "cfg.val1.val1") + raises(SlaveError, "cfg.val1.val2") + cfg.val3 = ['val1', 'val2', 'val3'] + assert cfg.val1.val2 == ['val1', 'val2', 'val'] + + +def test_callback_master_and_slaves_slave_cal2(): + val3 = StrOption('val3', "", ['val', 'val'], multi=True) + val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val3, False),)}) + val2 = StrOption('val2', "", ['val2', 'val2'], multi=True) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val3]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val3 == ['val', 'val'] + assert cfg.val1.val1 == ['val', 'val'] + assert cfg.val1.val2 == ['val2', 'val2'] + cfg.val3.pop(1) +# # cannot remove slave's value because master is calculated +# # so raise + raises(SlaveError, "cfg.val1.val1") + raises(SlaveError, "cfg.val1.val2") + cfg.val3 = ['val', 'val'] + assert cfg.val3 == ['val', 'val'] + assert cfg.val1.val1 == ['val', 'val'] + assert cfg.val1.val2 == ['val2', 'val2'] + raises(SlaveError, "cfg.val1.val1 = ['val']") + assert cfg.val3 == ['val', 'val'] + assert cfg.val1.val1 == ['val', 'val'] + assert cfg.val1.val2 == ['val2', 'val2'] + + def test_callback_master_and_slaves_slave_list(): val1 = StrOption('val1', "", multi=True) val2 = StrOption('val2', "", multi=True, callback=return_list) diff --git a/test/test_state.py b/test/test_state.py index ef46ce2..7693cb6 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -249,3 +249,46 @@ def test_state_values_owner(): delete_session('29090931') except ConfigError: pass + + +def test_state_unkown_setting_owner(): + """load an unknow _owner, should create it""" + assert not 'supernewuser' in owners.__dict__ + loads("""ccopy_reg +_reconstructor +p0 +(ctiramisu.setting +Settings +p1 +c__builtin__ +object +p2 +Ntp3 +Rp4 +(dp5 +S'_owner' +p6 +S'supernewuser' +p7 +sS'_p_' +p8 +g0 +(ctiramisu.storage.dictionary.setting +Settings +p9 +g2 +Ntp10 +Rp11 +(dp12 +S'_cache' +p13 +(dp14 +sS'_permissives' +p15 +(dp16 +sS'_properties' +p17 +(dp18 +sbsb. +.""") + assert 'supernewuser' in owners.__dict__ diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index ffdbb0a..cb3552a 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -152,7 +152,10 @@ def carry_out_calculation(option, config, callback, callback_params, opt) # get value try: - value = config._getattr(path, force_permissive=True) + value = config._getattr(path, force_permissive=True, validate=False) + # convert to list, not modifie this multi + if value.__class__.__name__ == 'Multi': + value = list(value) except PropertiesOptionError as err: if force_permissive: continue diff --git a/tiramisu/value.py b/tiramisu/value.py index a02196a..863f2a1 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -73,7 +73,7 @@ class Values(object): else: return value - def _getvalue(self, opt, path, validate=True): + def _getvalue(self, opt, path): """actually retrieves the value :param opt: the `option.Option()` object @@ -82,14 +82,9 @@ class Values(object): if not self._p_.hasvalue(path): # if there is no value value = self._getdefault(opt) - if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path, validate) else: # if there is a value value = self._p_.getvalue(path) - if opt.impl_is_multi() and not isinstance(value, Multi): - # load value so don't need to validate if is not a Multi - value = Multi(value, self.context, opt, path, validate=False) return value def get_modified_values(self): @@ -198,7 +193,7 @@ class Values(object): # ConfigError if properties did not raise. config_error = None force_permissives = None - # if value is callback and is not set + # if value has callback and is not set # or frozen with force_default_on_freeze if opt.impl_has_callback() and ( self._is_default_owner(path) or @@ -208,7 +203,7 @@ class Values(object): if (opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.slave): masterp = self._get_opt_path(opt.impl_get_master_slaves()) - mastervalue = getattr(context, masterp) + mastervalue = context._getattr(masterp, validate=validate) lenmaster = len(mastervalue) if lenmaster == 0: value = [] @@ -240,7 +235,10 @@ class Values(object): if opt.impl_is_multi(): value = Multi(value, self.context, opt, path, validate) else: - value = self._getvalue(opt, path, validate) + value = self._getvalue(opt, path) + if opt.impl_is_multi(): + # load value so don't need to validate if is not a Multi + value = Multi(value, self.context, opt, path, validate=validate) if config_error is None and validate: opt.impl_validate(value, context, 'validator' in setting) if config_error is None and self._is_default_owner(path) and \ @@ -266,10 +264,27 @@ class Values(object): context = self._getcontext() opt.impl_validate(value, context, 'validator' in context.cfgimpl_get_settings()) - if opt.impl_is_multi() and not isinstance(value, Multi): + if opt.impl_is_multi(): value = Multi(value, self.context, opt, path, setitem=True) + # Save old value + if opt.impl_get_multitype() == multitypes.master and \ + self._p_.hasvalue(path): + old_value = self._p_.getvalue(path) + old_owner = self._p_.getowner(path, None) + else: + old_value = undefined + old_owner = undefined self._setvalue(opt, path, value, force_permissive=force_permissive, is_write=is_write) + if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master: + try: + value._valid_master() + except Exception, err: + if old_value is not undefined: + self._p_.setvalue(path, old_value, old_owner) + else: + self._p_.resetvalue(path) + raise err def _setvalue(self, opt, path, value, force_permissive=False, force_properties=None, @@ -283,6 +298,8 @@ class Values(object): force_permissive=force_permissive, force_properties=force_properties) owner = context.cfgimpl_get_settings().getowner() + if isinstance(value, Multi): + value = list(value) self._p_.setvalue(path, value, owner) def getowner(self, opt): @@ -403,6 +420,8 @@ class Multi(list): :param opt: the option object that have this Multi value :param setitem: only if set a value """ + if isinstance(value, Multi): + raise ValueError(_('{0} is already a Multi ').format(opt._name)) self.opt = opt self.path = path if not isinstance(context, weakref.ReferenceType): @@ -412,8 +431,9 @@ class Multi(list): value = [value] if validate and self.opt.impl_get_multitype() == multitypes.slave: value = self._valid_slave(value, setitem) - elif validate and self.opt.impl_get_multitype() == multitypes.master: - self._valid_master(value) + elif not setitem and validate and \ + self.opt.impl_get_multitype() == multitypes.master: + self._valid_master() super(Multi, self).__init__(value) def _getcontext(self): @@ -433,12 +453,10 @@ class Multi(list): values = context.cfgimpl_get_values() masterp = context.cfgimpl_get_description().impl_get_path_by_opt( self.opt.impl_get_master_slaves()) - mastervalue = getattr(context, masterp) + mastervalue = context._getattr(masterp, validate=False) masterlen = len(mastervalue) valuelen = len(value) - is_default_owner = not values._is_default_owner(self.path) or setitem - if valuelen > masterlen or (valuelen < masterlen and - is_default_owner): + if valuelen > masterlen or (valuelen < masterlen and setitem): raise SlaveError(_("invalid len for the slave: {0}" " which has {1} as master").format( self.opt._name, masterp)) @@ -455,30 +473,12 @@ class Multi(list): #else: same len so do nothing return value - def _valid_master(self, value): - masterlen = len(value) + def _valid_master(self): + #masterlen = len(value) values = self._getcontext().cfgimpl_get_values() for slave in self.opt._master_slaves: path = values._get_opt_path(slave) - if not values._is_default_owner(path): - value_slave = values._getvalue(slave, path) - if len(value_slave) > masterlen: - raise SlaveError(_("invalid len for the master: {0}" - " which has {1} as slave with" - " greater len").format( - self.opt._name, slave._name)) - elif len(value_slave) < masterlen: - for num in range(0, masterlen - len(value_slave)): - if slave.impl_has_callback(): - # if callback add a value, but this value will not - # change anymore automaticly (because this value - # has owner) - index = value_slave.__len__() - value_slave.append( - values._getcallback_value(slave, index=index), - force=True) - else: - value_slave.append(undefined, force=True) + Multi(values._getvalue(slave, path), self.context, slave, path) def __setitem__(self, index, value): self._validate(value, index) @@ -518,16 +518,15 @@ class Multi(list): dvalue = values._getcallback_value(slave, index=index) else: dvalue = slave.impl_getdefault_multi() - old_value = values.getitem(slave, path, + old_value = values.getitem(slave, path, validate=False, validate_properties=False) - if len(old_value) < self.__len__(): - values.getitem(slave, path, - validate_properties=False).append( - dvalue, force=True) - else: - values.getitem(slave, path, - validate_properties=False)[ - index] = dvalue + if len(old_value) + 1 != self.__len__(): + raise SlaveError(_("invalid len for the slave: {0}" + " which has {1} as master").format( + self.opt._name, self.__len__())) + values.getitem(slave, path, validate=False, + validate_properties=False).append( + dvalue, force=True) def sort(self, cmp=None, key=None, reverse=False): if self.opt.impl_get_multitype() in [multitypes.slave, @@ -592,12 +591,12 @@ class Multi(list): if self.opt.impl_get_multitype() == multitypes.slave: raise SlaveError(_("cannot pop a value on a multi option {0}" " which is a slave").format(self.opt._name)) - elif self.opt.impl_get_multitype() == multitypes.master: + if self.opt.impl_get_multitype() == multitypes.master: for slave in self.opt.impl_get_master_slaves(): values = context.cfgimpl_get_values() if not values.is_default_owner(slave): #get multi without valid properties - values.getitem(slave, + values.getitem(slave, validate=False, validate_properties=False ).pop(index, force=True) #set value without valid properties From ced260046c89f2e98a8ec26b859e9750e512749c Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 2 Feb 2014 18:21:09 +0100 Subject: [PATCH 07/15] test for multi --- test/test_multi.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/test_multi.py diff --git a/test/test_multi.py b/test/test_multi.py new file mode 100644 index 0000000..42fd8e1 --- /dev/null +++ b/test/test_multi.py @@ -0,0 +1,21 @@ +# coding: utf-8 +import autopath +from tiramisu.value import Multi +from tiramisu.option import IntOption, OptionDescription +from tiramisu.config import Config +from tiramisu.error import ConfigError + +import weakref +from py.test import raises + + +def test_multi(): + i = IntOption('int', '', multi=True) + o = OptionDescription('od', '', [i]) + c = Config(o) + multi = Multi([1,2,3], weakref.ref(c), i, 'int') + raises(ValueError, "Multi([1,2,3], c, i, 'int')") + raises(ValueError, "Multi(multi, weakref.ref(c), i, 'int')") + assert c is multi._getcontext() + del(c) + raises(ConfigError, "multi._getcontext()") From 26158fc3c41b5cd9e5125a68588f9c9b1c1d1cf5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 2 Feb 2014 18:33:21 +0100 Subject: [PATCH 08/15] update test for metaconfig serialization --- test/test_state.py | 5 ++--- tiramisu/config.py | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/test/test_state.py b/test/test_state.py index e7777f1..fd547ef 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -5,6 +5,7 @@ from tiramisu.setting import owners from tiramisu.storage import delete_session from tiramisu.error import ConfigError from pickle import dumps, loads +from py.test import raises def return_value(value=None): @@ -288,9 +289,7 @@ def test_state_metaconfig(): 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) + raises(ConfigError, "dumps(meta)") try: delete_session('29090935') delete_session('29090936') diff --git a/tiramisu/config.py b/tiramisu/config.py index 2c1a6b1..e8fc3ca 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -540,8 +540,6 @@ class _CommonConfig(SubConfig): # ----- state def __getstate__(self): if self._impl_meta is not None: - #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__: From 8d10ad40022f35267a3edd7b2968cf784bfe93cf Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 2 Feb 2014 22:47:46 +0100 Subject: [PATCH 09/15] add tests --- test/test_config.py | 26 +++++++++++++++++++++++++- test/test_config_api.py | 31 +++++++++++++++++++++++++++++++ test/test_metaconfig.py | 18 ++++++++++++++++++ test/test_parsing_group.py | 13 +++++++++++++ tiramisu/config.py | 10 +++------- 5 files changed, 90 insertions(+), 8 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 6db3fcf..8de8a57 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -6,10 +6,11 @@ import autopath from py.test import raises -from tiramisu.config import Config +from tiramisu.config import Config, SubConfig from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ BoolOption, UnicodeOption, OptionDescription from tiramisu.error import ConflictError, ConfigError +import weakref def make_description(): @@ -294,3 +295,26 @@ def test_delete_config_with_subconfig(): raises(ConfigError, 'val[multi]') raises(ConfigError, 'setting[test]') raises(ConfigError, 'sub.make_dict()') + + +def test_config_weakref(): + o = OptionDescription('val', '', []) + o2 = OptionDescription('val', '', [o]) + c = Config(o2) + SubConfig(o, weakref.ref(c)) + raises(ValueError, "SubConfig(o, c)") + s = SubConfig(o, weakref.ref(c)) + assert s._cfgimpl_get_context() == c + del(c) + raises(ConfigError, "s._cfgimpl_get_context()") + + +def test_config_str(): + gcdummy = BoolOption('dummy', 'dummy', default=False) + gcdummy1 = BoolOption('dummy1', 'dummy', default=False, properties=('disabled',)) + o = OptionDescription('o', '', [gcdummy, gcdummy1]) + descr = OptionDescription('tiramisu', '', [o]) + cfg = Config(descr) + cfg.read_only() + str(cfg) + str(cfg.o) diff --git a/test/test_config_api.py b/test/test_config_api.py index db40c57..75920eb 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -67,6 +67,17 @@ def test_iter_config(): [('string', 'string'), ('string2', 'string2')] +def test_iter_config_property(): + "iteration on config object" + s = StrOption("string", "", default="string", properties=('disabled',)) + s2 = StrOption("string2", "", default="string2") + descr = OptionDescription("options", "", [s, s2]) + config = Config(descr) + config.read_only() + assert [(name, value) for name, value in config] == \ + [('string2', 'string2')] + + def test_iter_subconfig(): "iteration on config sub object" descr = make_description() @@ -177,3 +188,23 @@ def test_filename(): c.a = u'tmp/text.txt' raises(ValueError, "c.a = u'/tmp/with space.txt'") raises(ValueError, "c.a = u'/tmp/with$.txt'") + + +def test_iter_all(): + s = StrOption("string", "", default="string") + s2 = StrOption("string2", "", default="string2") + descr = OptionDescription("options", "", [s, s2]) + config = Config(descr) + assert list(config.iter_all()) == [('string', 'string'), ('string2', 'string2')] + for i in config.iter_all(): + #test StopIteration + break + + +def test_iter_all_prop(): + s = StrOption("string", "", default="string", properties=('disabled',)) + s2 = StrOption("string2", "", default="string2") + descr = OptionDescription("options", "", [s, s2]) + config = Config(descr) + config.read_only() + assert list(config.iter_all()) == [('string2', 'string2')] diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py index 6986edc..9409d85 100644 --- a/test/test_metaconfig.py +++ b/test/test_metaconfig.py @@ -171,3 +171,21 @@ def test_meta_path(): meta = make_description() assert meta._impl_path is None assert meta.od1._impl_path == 'od1' + + +def test_meta_unconsistent(): + 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) + conf3 = Config(od2) + conf4 = Config(od1) + meta = MetaConfig([conf1, conf2]) + meta.cfgimpl_get_settings().setowner(owners.meta) + raises(TypeError, 'MetaConfig("string")') + raises(ValueError, "MetaConfig([conf1, conf3])") + raises(ValueError, "MetaConfig([conf3, conf4])") diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 7ecd860..c3b6ffc 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -94,6 +94,19 @@ def test_iter_on_groups(): result = list(config.creole.iter_groups(group_type=groups.family)) group_names = [res[0] for res in result] assert group_names == ['general', 'interface1'] + for i in config.creole.iter_groups(group_type=groups.family): + #test StopIteration + break + + +def test_iter_on_groups_props(): + descr = make_description() + config = Config(descr) + config.read_write() + config.cfgimpl_get_settings()[descr.creole.interface1].append('disabled') + result = list(config.creole.iter_groups(group_type=groups.family)) + group_names = [res[0] for res in result] + assert group_names == ['general'] def test_iter_on_empty_group(): diff --git a/tiramisu/config.py b/tiramisu/config.py index e8fc3ca..e3ac94f 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -149,8 +149,6 @@ class SubConfig(object): except UnicodeEncodeError: lines.append("{0} = {1}".format(name, value.encode(default_encoding))) - except PropertiesOptionError: - pass return '\n'.join(lines) __repr__ = __str__ @@ -540,7 +538,7 @@ class _CommonConfig(SubConfig): # ----- state def __getstate__(self): if self._impl_meta is not None: - raise ConfigError('cannot serialize Config with MetaConfig') + raise ConfigError(_('cannot serialize Config with MetaConfig')) slots = set() for subclass in self.__class__.__mro__: if subclass is not object: @@ -554,8 +552,8 @@ class _CommonConfig(SubConfig): pass storage = self._impl_values._p_._storage if not storage.serializable: - raise ConfigError('this storage is not serialisable, could be a ' - 'none persistent storage') + raise ConfigError(_('this storage is not serialisable, could be a ' + 'none persistent storage')) state['_storage'] = {'session_id': storage.session_id, 'persistent': storage.persistent} state['_impl_setting'] = _impl_getstate_setting() @@ -742,6 +740,4 @@ def mandatory_warnings(config): except PropertiesOptionError as err: if err.proptype == ['mandatory']: yield path - except ConfigError: - pass config.cfgimpl_reset_cache(only=('values',)) From a067d2cdd9f27a153f1a994567bbcad4f907b91d Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 4 Feb 2014 21:14:30 +0100 Subject: [PATCH 10/15] add some tests --- test/test_config_api.py | 19 ++++++++++++++++++- test/test_metaconfig.py | 30 +++++++++++++++++++++--------- test/test_option_owner.py | 1 + tiramisu/config.py | 33 ++++++++++++--------------------- tiramisu/option.py | 30 +++++++++++++++++++----------- tiramisu/value.py | 2 ++ 6 files changed, 73 insertions(+), 42 deletions(-) diff --git a/test/test_config_api.py b/test/test_config_api.py index 75920eb..f5bc2c1 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -4,7 +4,8 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ - BoolOption, FilenameOption, OptionDescription + BoolOption, FilenameOption, UnicodeOption, SymLinkOption, IPOption, \ + PortOption, OptionDescription def make_description(): @@ -150,6 +151,7 @@ def test_find_in_config(): # not OptionDescription raises(AttributeError, "conf.find_first(byname='gc')") raises(AttributeError, "conf.gc.find_first(byname='gc2')") + raises(ValueError, "conf.find(byname='bool', type_='unknown')") def test_find_multi(): @@ -208,3 +210,18 @@ def test_iter_all_prop(): config = Config(descr) config.read_only() assert list(config.iter_all()) == [('string2', 'string2')] + + +def test_invalid_option(): + raises(TypeError, "ChoiceOption('a', '', [1, 2])") + raises(TypeError, "ChoiceOption('a', '', 1)") + raises(TypeError, "ChoiceOption('a', '', (1,), open_values='string')") + raises(ValueError, "ChoiceOption('a', '', (1,), 3)") + raises(ValueError, "FloatOption('a', '', 'string')") + raises(ValueError, "UnicodeOption('a', '', 1)") + raises(ValueError, "SymLinkOption('a', 'string')") + raises(ValueError, "IPOption('a', '', 1)") + raises(ValueError, "IPOption('a', '', 'string')") + raises(ValueError, "PortOption('a', '', 'string')") + raises(ValueError, "PortOption('a', '', '11:12:13', allow_range=True)") + raises(ValueError, "PortOption('a', '', 11111111111111111111)") diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py index 9409d85..a8fc395 100644 --- a/test/test_metaconfig.py +++ b/test/test_metaconfig.py @@ -5,7 +5,7 @@ from py.test import raises from tiramisu.setting import owners from tiramisu.config import Config, GroupConfig, MetaConfig from tiramisu.option import IntOption, OptionDescription -from tiramisu.error import ConfigError +from tiramisu.error import ConfigError, PropertiesOptionError owners.addowner('meta') @@ -15,10 +15,14 @@ def make_description(): i2 = IntOption('i2', '', default=1) i3 = IntOption('i3', '') i4 = IntOption('i4', '', default=2) - od1 = OptionDescription('od1', '', [i1, i2, i3, i4]) + i5 = IntOption('i5', '', default=[2], multi=True) + i6 = IntOption('i6', '', properties=('disabled',)) + od1 = OptionDescription('od1', '', [i1, i2, i3, i4, i5, i6]) od2 = OptionDescription('od2', '', [od1]) conf1 = Config(od2) conf2 = Config(od2) + conf1.read_write() + conf2.read_write() meta = MetaConfig([conf1, conf2]) meta.cfgimpl_get_settings().setowner(owners.meta) return meta @@ -29,7 +33,7 @@ def make_description(): #FIXME serialization def test_none(): meta = make_description() - conf1, conf2 = meta._impl_children + conf1, conf2 = meta.cfgimpl_get_children() assert conf1.od1.i3 is conf2.od1.i3 is None assert conf1.getowner(conf1.unwrap_from_path('od1.i3')) is conf2.getowner(conf2.unwrap_from_path('od1.i3')) is owners.default meta.od1.i3 = 3 @@ -58,7 +62,7 @@ def test_none(): def test_default(): meta = make_description() - conf1, conf2 = meta._impl_children + conf1, conf2 = meta.cfgimpl_get_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.od1.i2 = 3 @@ -87,7 +91,7 @@ def test_default(): def test_contexts(): meta = make_description() - conf1, conf2 = meta._impl_children + conf1, conf2 = meta.cfgimpl_get_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.setattrs('od1.i2', 6) @@ -101,14 +105,15 @@ def test_find(): 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} + assert meta.make_dict() == {'od1.i4': 2, 'od1.i1': None, 'od1.i3': None, + 'od1.i2': 1, 'od1.i5': [2], 'od1.i6': None} def test_meta_meta(): meta1 = make_description() meta2 = MetaConfig([meta1]) meta2.cfgimpl_get_settings().setowner(owners.meta) - conf1, conf2 = meta1._impl_children + conf1, conf2 = meta1.cfgimpl_get_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 meta2.od1.i2 = 3 @@ -142,15 +147,21 @@ def test_meta_meta_set(): meta1 = make_description() meta2 = MetaConfig([meta1]) meta2.cfgimpl_get_settings().setowner(owners.meta) - conf1, conf2 = meta1._impl_children + conf1, conf2 = meta1.cfgimpl_get_children() meta2.setattrs('od1.i1', 7) + #PropertiesOptionError + meta2.setattrs('od1.i6', 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_firsts(byname='i1', byvalue=7) conf1.od1.i1 = 8 + assert [conf1, conf2] == meta2.find_firsts(byname='i1') assert [conf2] == meta2.find_firsts(byname='i1', byvalue=7) assert [conf1] == meta2.find_firsts(byname='i1', byvalue=8) + assert [conf1, conf2] == meta2.find_firsts(byname='i5', byvalue=2) raises(AttributeError, "meta2.find_firsts(byname='i1', byvalue=10)") + raises(AttributeError, "meta2.find_firsts(byname='not', byvalue=10)") + raises(AttributeError, "meta2.find_firsts(byname='i6')") def test_not_meta(): @@ -159,9 +170,10 @@ def test_not_meta(): od2 = OptionDescription('od2', '', [od1]) conf1 = Config(od2) conf2 = Config(od2) + raises(ValueError, "GroupConfig(conf1)") meta = GroupConfig([conf1, conf2]) raises(ConfigError, 'meta.od1.i1') - conf1, conf2 = meta._impl_children + conf1, conf2 = meta.cfgimpl_get_children() 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_option_owner.py b/test/test_option_owner.py index b758e91..d4c3f6b 100644 --- a/test/test_option_owner.py +++ b/test/test_option_owner.py @@ -36,6 +36,7 @@ def test_default_owner(): cfg = Config(descr) assert cfg.dummy is False assert cfg.getowner(gcdummy) == 'default' + raises(TypeError, "cfg.getowner('gcdummy')") cfg.dummy = True assert cfg.getowner(gcdummy) == owners.user diff --git a/tiramisu/config.py b/tiramisu/config.py index e3ac94f..c9fb992 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -296,12 +296,9 @@ class SubConfig(object): :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 + if byname is None or path == byname or \ + path.endswith('.' + byname): + return True return False def _filter_by_value(): @@ -452,22 +449,16 @@ class SubConfig(object): 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 + pathsvalues += getattr(self, path).make_dict(flatten, + _currpath + + path.split('.')) else: - try: - value = self._getattr(opt._name) - if flatten: - name = opt._name - else: - name = '.'.join(_currpath + [opt._name]) - pathsvalues.append((name, value)) - except PropertiesOptionError: - pass # this just a hidden or disabled option + value = self._getattr(opt._name) + if flatten: + name = opt._name + else: + name = '.'.join(_currpath + [opt._name]) + pathsvalues.append((name, value)) def cfgimpl_get_path(self): descr = self.cfgimpl_get_description() diff --git a/tiramisu/option.py b/tiramisu/option.py index ad99416..409d6eb 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -662,7 +662,7 @@ class ChoiceOption(Option): return self._open_values def _validate(self, value): - if not self._open_values and not value in self._values: + if not self.impl_is_openvalues() and not value in self.impl_get_values(): raise ValueError(_('value {0} is not permitted, ' 'only {1} is allowed' '').format(value, self._values)) @@ -782,9 +782,13 @@ class IPOption(Option): def _validate(self, value): # sometimes an ip term starts with a zero # but this does not fit in some case, for example bind does not like it - for val in value.split('.'): - if val.startswith("0") and len(val) > 1: - raise ValueError(_('invalid IP')) + try: + for val in value.split('.'): + if val.startswith("0") and len(val) > 1: + raise ValueError(_('invalid IP')) + except AttributeError: + #if integer for example + raise ValueError(_('invalid IP')) # 'standard' validation try: IP('{0}/32'.format(value)) @@ -856,18 +860,22 @@ class PortOption(Option): if self._allow_range and ":" in str(value): value = str(value).split(':') if len(value) != 2: - raise ValueError('invalid part, range must have two values ' - 'only') + raise ValueError(_('invalid part, range must have two values ' + 'only')) if not value[0] < value[1]: - raise ValueError('invalid port, first port in range must be' - ' smaller than the second one') + raise ValueError(_('invalid port, first port in range must be' + ' smaller than the second one')) else: value = [value] for val in value: - if not self._min_value <= int(val) <= self._max_value: - raise ValueError('invalid port, must be an between {0} and {1}' - ''.format(self._min_value, self._max_value)) + try: + if not self._min_value <= int(val) <= self._max_value: + raise ValueError(_('invalid port, must be an between {0} ' + 'and {1}').format(self._min_value, + self._max_value)) + except ValueError: + raise ValueError(_('invalid port')) class NetworkOption(Option): diff --git a/tiramisu/value.py b/tiramisu/value.py index 863f2a1..ed70f90 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -66,6 +66,8 @@ class Values(object): meta = self._getcontext().cfgimpl_get_meta() if meta is not None: value = meta.cfgimpl_get_values()[opt] + if isinstance(value, Multi): + value = list(value) else: value = opt.impl_getdefault() if opt.impl_is_multi(): From c52b2f84f42dbfab7c9ab8ee8459d271b45d9593 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 4 Feb 2014 21:40:07 +0100 Subject: [PATCH 11/15] if option with requires has a property, calculated properties are store in storage --- test/test_requires.py | 22 ++++++++++++++++++++++ tiramisu/setting.py | 10 +++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/test/test_requires.py b/test/test_requires.py index 4cf9372..ff9c2a6 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -24,6 +24,28 @@ def test_requires(): except PropertiesOptionError as err: props = err.proptype assert props == ['disabled'] + c.activate_service = True + c.ip_address_service + + +def test_requires_with_requires(): + a = BoolOption('activate_service', '', True) + b = IPOption('ip_address_service', '', + requires=[{'option': a, 'expected': False, 'action': 'disabled'}]) + od = OptionDescription('service', '', [a, b]) + c = Config(od) + c.read_write() + c.cfgimpl_get_settings()[b].append('test') + c.ip_address_service + c.activate_service = False + props = [] + try: + c.ip_address_service + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + c.activate_service = True + c.ip_address_service def test_requires_invalid(): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 101b551..38cf7b5 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -372,7 +372,7 @@ class Settings(object): def _getproperties(self, opt=None, path=None, is_apply_req=True): if opt is None: - props = self._p_.getproperties(path, default_properties) + props = copy(self._p_.getproperties(path, default_properties)) else: if path is None: raise ValueError(_('if opt is not None, path should not be' @@ -383,8 +383,8 @@ class Settings(object): ntime = int(time()) is_cached, props = self._p_.getcache(path, ntime) if is_cached: - return props - props = self._p_.getproperties(path, opt._properties) + return copy(props) + props = copy(self._p_.getproperties(path, opt._properties)) if is_apply_req: props |= self.apply_requires(opt, path) if 'cache' in self: @@ -446,8 +446,8 @@ class Settings(object): (typically with the `frozen` property) """ # opt properties - properties = copy(self._getproperties(opt_or_descr, path)) - self_properties = copy(self._getproperties()) + properties = self._getproperties(opt_or_descr, path) + self_properties = self._getproperties() # remove opt permissive # permissive affect option's permission with or without permissive # global property From 72f06bc29d8daedad27c43ac32015b7860aabf70 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 6 Feb 2014 19:19:48 +0100 Subject: [PATCH 12/15] properties option in consistencies are now allowed --- test/test_option_consistency.py | 11 +++++++++++ tiramisu/option.py | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 4db3410..a7ff41c 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -283,3 +283,14 @@ def test_consistency_not_all(): c.a = ['192.168.1.0'] c.b = ['255.255.255.0'] c.c = ['192.168.1.255'] + + +def test_consistency_permissive(): + a = IntOption('a', '', 1) + b = IntOption('b', '', 2, properties=('hidden',)) + od = OptionDescription('od', '', [a, b]) + a.impl_add_consistency('not_equal', b) + c = Config(od) + c.cfgimpl_get_settings().setpermissive(('hidden',)) + c.read_write() + c.a = 1 diff --git a/tiramisu/option.py b/tiramisu/option.py index 409d6eb..aafd43b 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -367,7 +367,8 @@ class Option(BaseOption): #if context, calculate value, otherwise get default value if context is not None: opt_value = context._getattr( - descr.impl_get_path_by_opt(opt), validate=False) + descr.impl_get_path_by_opt(opt), validate=False, + force_permissive=True) else: opt_value = opt.impl_getdefault() @@ -873,7 +874,7 @@ class PortOption(Option): if not self._min_value <= int(val) <= self._max_value: raise ValueError(_('invalid port, must be an between {0} ' 'and {1}').format(self._min_value, - self._max_value)) + self._max_value)) except ValueError: raise ValueError(_('invalid port')) From e7531e1fda5340b61b6766aaae900d588d05e25a Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 6 Feb 2014 22:17:20 +0100 Subject: [PATCH 13/15] more tests --- test/test_config_api.py | 32 ++++++++++++++++- test/test_config_domain.py | 12 +++++-- test/test_option_consistency.py | 28 +++++++++++++++ test/test_state.py | 8 ++++- tiramisu/option.py | 61 +++++++++------------------------ 5 files changed, 93 insertions(+), 48 deletions(-) diff --git a/test/test_config_api.py b/test/test_config_api.py index f5bc2c1..4be9e80 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -5,7 +5,9 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ BoolOption, FilenameOption, UnicodeOption, SymLinkOption, IPOption, \ - PortOption, OptionDescription + PortOption, NetworkOption, NetmaskOption, BroadcastOption, \ + DomainnameOption, OptionDescription +from tiramisu.error import PropertiesOptionError def make_description(): @@ -212,6 +214,22 @@ def test_iter_all_prop(): assert list(config.iter_all()) == [('string2', 'string2')] +def test_impl_getpaths(): + s = StrOption("string", "", default="string", properties=('disabled',)) + s2 = StrOption("string2", "", default="string2") + s3 = StrOption("string3", "", default="string3") + s4 = StrOption("string4", "", default="string4", properties=('hidden',)) + od = OptionDescription('od', '', [s3, s4]) + descr = OptionDescription("options", "", [s, s2, od]) + config = Config(descr) + assert ['string', 'string2', 'od.string3', 'od.string4'] == config.cfgimpl_get_description().impl_getpaths() + assert ['string', 'string2', 'od', 'od.string3', 'od.string4'] == config.cfgimpl_get_description().impl_getpaths(include_groups=True) + config.read_write() + raises(PropertiesOptionError, "config.od.string4") + assert ['string', 'string2', 'od.string3', 'od.string4'] == config.cfgimpl_get_description().impl_getpaths() + assert ['string', 'string2', 'od', 'od.string3', 'od.string4'] == config.cfgimpl_get_description().impl_getpaths(include_groups=True) + + def test_invalid_option(): raises(TypeError, "ChoiceOption('a', '', [1, 2])") raises(TypeError, "ChoiceOption('a', '', 1)") @@ -225,3 +243,15 @@ def test_invalid_option(): raises(ValueError, "PortOption('a', '', 'string')") raises(ValueError, "PortOption('a', '', '11:12:13', allow_range=True)") raises(ValueError, "PortOption('a', '', 11111111111111111111)") + raises(ValueError, "PortOption('a', '', allow_zero=True, allow_wellknown=False, allow_registred=True, allow_private=False)") + raises(ValueError, "PortOption('a', '', allow_zero=True, allow_wellknown=True, allow_registred=False, allow_private=True)") + raises(ValueError, "PortOption('a', '', allow_zero=True, allow_wellknown=False, allow_registred=False, allow_private=True)") + raises(ValueError, "PortOption('a', '', allow_zero=True, allow_wellknown=False, allow_registred=True, allow_private=True)") + raises(ValueError, "PortOption('a', '', allow_zero=False, allow_wellknown=False, allow_registred=False, allow_private=False)") + raises(ValueError, "NetworkOption('a', '', 'string')") + raises(ValueError, "NetmaskOption('a', '', 'string')") + raises(ValueError, "BroadcastOption('a', '', 'string')") + raises(ValueError, "DomainnameOption('a', '', 'string')") + raises(ValueError, "DomainnameOption('a', '', type_='string')") + raises(ValueError, "DomainnameOption('a', '', allow_ip='string')") + raises(ValueError, "DomainnameOption('a', '', allow_without_dot='string')") diff --git a/test/test_config_domain.py b/test/test_config_domain.py index f4579a6..8fcf1f1 100644 --- a/test/test_config_domain.py +++ b/test/test_config_domain.py @@ -7,9 +7,9 @@ from tiramisu.option import DomainnameOption, EmailOption, URLOption, OptionDesc def test_domainname(): d = DomainnameOption('d', '') - e = DomainnameOption('e', '', "toto.com") f = DomainnameOption('f', '', allow_without_dot=True) - od = OptionDescription('a', '', [d, f]) + g = DomainnameOption('g', '', allow_ip=True) + od = OptionDescription('a', '', [d, f, g]) c = Config(od) c.read_write() c.d = 'toto.com' @@ -24,6 +24,12 @@ def test_domainname(): # c.f = 'toto.com' c.f = 'toto' + raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnameanditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnametoolongthathavemorethanmaximumsizeforatruedomainnameanditsnoteasytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowiendityeah'") + raises(ValueError, "c.f = 'd'") + # + c.g = 'toto.com' + c.g = '192.168.1.0' + c.g = '192.168.1.29' def test_domainname_netbios(): @@ -56,6 +62,7 @@ def test_email(): c.e = 'root@foo.com' raises(ValueError, "c.e = 'root'") raises(ValueError, "c.e = 'root@domain'") + raises(ValueError, "c.e = 'root[]@domain'") def test_url(): @@ -76,3 +83,4 @@ def test_url(): c.u = 'https://foo.com:8443' c.u = 'https://foo.com:8443/' c.u = 'https://foo.com:8443/index.html' + raises(ValueError, "c.u = 'https://foo.com:84438989'") diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index a7ff41c..1beb4bb 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -169,6 +169,18 @@ def test_consistency_network_netmask(): raises(ValueError, "c.a = '192.168.1.1'") +def test_consistency_ip_netmask_network_error(): + a = IPOption('a', '') + b = NetworkOption('b', '') + c = NetmaskOption('c', '') + od = OptionDescription('od', '', [a, b, c]) + c.impl_add_consistency('ip_netmask', a, b) + c = Config(od) + c.a = '192.168.1.1' + c.b = '192.168.1.0' + raises(ConfigError, "c.c = '255.255.255.0'") + + def test_consistency_ip_netmask_error_multi(): a = IPOption('a', '', multi=True) b = NetmaskOption('b', '') @@ -217,6 +229,8 @@ def test_consistency_ip_netmask_multi_master(): c.b = ['255.255.255.255'] c.b = ['255.255.255.0'] raises(ValueError, "c.a = ['192.168.1.0']") + c.a = ['192.168.1.128'] + raises(ValueError, "c.b = ['255.255.255.128']") c.a = ['192.168.1.2', '192.168.1.3'] @@ -260,6 +274,20 @@ def test_consistency_broadcast(): c.c[1] = '192.168.2.255' +def test_consistency_broadcast_error(): + a = NetworkOption('a', '', multi=True) + b = NetmaskOption('b', '', multi=True) + c = BroadcastOption('c', '', multi=True) + od = OptionDescription('a', '', [a, b, c]) + od.impl_set_group_type(groups.master) + b.impl_add_consistency('network_netmask', a) + c.impl_add_consistency('broadcast', a) + c = Config(od) + c.a = ['192.168.1.0'] + c.b = ['255.255.255.0'] + raises(ConfigError, "c.c = ['192.168.1.255']") + + def test_consistency_broadcast_default(): a = NetworkOption('a', '', '192.168.1.0') b = NetmaskOption('b', '', '255.255.255.128') diff --git a/test/test_state.py b/test/test_state.py index fd547ef..03be06e 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -1,3 +1,5 @@ +import autopath + from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \ IntOption, OptionDescription from tiramisu.config import Config, GroupConfig, MetaConfig @@ -133,7 +135,6 @@ def _diff_conf(cfg1, cfg2): def test_diff_opt(): b = BoolOption('b', '') u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}]) - #u.impl_add_consistency('not_equal', b) s = SymLinkOption('s', u) o = OptionDescription('o', '', [b, u, s]) o1 = OptionDescription('o1', '', [o]) @@ -147,6 +148,11 @@ def test_diff_opt(): _diff_opt(o1.o.s, q.o.s) +def test_only_optiondescription(): + b = BoolOption('b', '') + raises(SystemError, "a = dumps(b)") + + def test_diff_opt_cache(): b = BoolOption('b', '') u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}]) diff --git a/tiramisu/option.py b/tiramisu/option.py index aafd43b..af7a67a 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -354,9 +354,6 @@ class Option(BaseOption): """ if context is not None: descr = context.cfgimpl_get_description() - #option is also in all_cons_opts - if option not in all_cons_opts: - raise ConfigError(_('option not in all_cons_opts')) all_cons_vals = [] for opt in all_cons_opts: @@ -587,31 +584,15 @@ class Option(BaseOption): consistencies = self._state_consistencies else: consistencies = self._consistencies - if isinstance(consistencies, list): - new_value = [] - for consistency in consistencies: - values = [] - for obj in consistency[1]: - if load: - values.append(descr.impl_get_opt_by_path(obj)) - else: - values.append(descr.impl_get_path_by_opt(obj)) - new_value.append((consistency[0], tuple(values))) - - else: - new_value = {} - for key, _consistencies in consistencies.items(): - new_value[key] = [] - for key_cons, _cons in _consistencies: - _list_cons = [] - for _con in _cons: - if load: - _list_cons.append( - descr.impl_get_opt_by_path(_con)) - else: - _list_cons.append( - descr.impl_get_path_by_opt(_con)) - new_value[key].append((key_cons, tuple(_list_cons))) + new_value = [] + for consistency in consistencies: + values = [] + for obj in consistency[1]: + if load: + values.append(descr.impl_get_opt_by_path(obj)) + else: + values.append(descr.impl_get_path_by_opt(obj)) + new_value.append((consistency[0], tuple(values))) if load: del(self._state_consistencies) self._consistencies = new_value @@ -932,19 +913,14 @@ class NetmaskOption(Option): IP('{0}/{1}'.format(val_ipnetwork, val_netmask), make_net=not make_net) except ValueError: - if not make_net: - msg = _("invalid network {0} ({1}) " - "with netmask {2}," - " this network is an IP") + pass else: if make_net: msg = _("invalid IP {0} ({1}) with netmask {2}," " this IP is a network") except ValueError: - if make_net: - msg = _('invalid IP {0} ({1}) with netmask {2}') - else: + if not make_net: msg = _('invalid network {0} ({1}) with netmask {2}') if msg is not None: raise ValueError(msg.format(val_ipnetwork, opts[1]._name, @@ -1035,8 +1011,8 @@ class DomainnameOption(Option): if self._type == 'domainname' and not self._allow_without_dot and \ '.' not in value: raise ValueError(_("invalid domainname, must have dot")) - if len(value) > 255: - raise ValueError(_("invalid domainname's length (max 255)")) + if len(value) > 255: + raise ValueError(_("invalid domainname's length (max 255)")) if len(value) < 2: raise ValueError(_("invalid domainname's length (min 2)")) if not self._domain_re.search(value): @@ -1313,13 +1289,10 @@ class OptionDescription(BaseOption): if consistencies is not None: for func, all_cons_opts in consistencies: #all_cons_opts[0] is the option where func is set - ret = all_cons_opts[0]._launch_consistency(func, option, - value, - context, index, - all_cons_opts) - if ret is False: - return False - return True + all_cons_opts[0]._launch_consistency(func, option, + value, + context, index, + all_cons_opts) def _impl_getstate(self, descr=None): """enables us to export into a dict From 8d751ecc9beca1a328f0e52a52325ea9d7d8c559 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Mon, 17 Feb 2014 18:36:29 +0100 Subject: [PATCH 14/15] valid port type before compare to min and max value --- tiramisu/option.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tiramisu/option.py b/tiramisu/option.py index af7a67a..d46c8c9 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -852,12 +852,13 @@ class PortOption(Option): for val in value: try: - if not self._min_value <= int(val) <= self._max_value: - raise ValueError(_('invalid port, must be an between {0} ' - 'and {1}').format(self._min_value, - self._max_value)) + int(val) except ValueError: raise ValueError(_('invalid port')) + if not self._min_value <= int(val) <= self._max_value: + raise ValueError(_('invalid port, must be an between {0} ' + 'and {1}').format(self._min_value, + self._max_value)) class NetworkOption(Option): From bf0501b1d623fcc76232e79f0fd7518d35060dd5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 20 Feb 2014 14:27:29 +0100 Subject: [PATCH 15/15] tiramisu/setting.py : properties store in cache was a reference to a list modified in validation, should copy properties in cache --- test/test_option_setting.py | 11 +++++++++++ tiramisu/setting.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/test_option_setting.py b/test/test_option_setting.py index ed5f7d7..94755e8 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -367,3 +367,14 @@ def test_reset_multiple(): setting[option].append('test') setting.reset(all_properties=True) setting.reset(all_properties=True) + + +def test_properties_cached(): + b1 = BoolOption("b1", "", properties=('test',)) + descr = OptionDescription("opt", "", [OptionDescription("sub", "", [b1])]) + c = Config(descr) + c.read_write() + setting = c.cfgimpl_get_settings() + option = c.cfgimpl_get_description().sub.b1 + c._setattr('sub.b1', True, force_permissive=True) + assert str(setting[b1]) == "['test']" diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 38cf7b5..e2ddb7d 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -392,7 +392,7 @@ class Settings(object): if ntime is None: ntime = int(time()) ntime = ntime + expires_time - self._p_.setcache(path, props, ntime) + self._p_.setcache(path, copy(props), ntime) return props def append(self, propname):