diff --git a/doc/config.txt b/doc/config.txt index 3c6d9bc..f1ab1ce 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -10,8 +10,6 @@ Tiramisu is made of almost three main objects : - :class:`tiramisu.option.OptionDescription` is the shema, the option's structure - :class:`tiramisu.config.Config` which is the whole configuration entry point -.. image:: config.png - Accessing the `Option`'s ------------------------- @@ -47,9 +45,13 @@ object is returned, and if no `Option` has been declared in the The `Option` objects (in this case the :class:`~tiramisu.option.BoolOption`), are organized into a tree into nested -:class:`~tiramisu.option.OptionDescription` objects. Every option has a name, -as does every option group. The parts of the full name of the option are -separated by dots: e.g. ``cfg.optgroup.optname``. +:class:`~tiramisu.option.OptionDescription` objects. + +.. image:: config.png + +Every option has a name, as does every option group. The parts +of the full name of the option are separated by dots: e.g. +``cfg.optgroup.optname``. Let's make the protocol of accessing a `Config`'s attribute explicit (because explicit is better than implicit): @@ -362,6 +364,10 @@ read/write or read only mode:: >>> c.cfgimpl_get_settings().remove('unknown') >>> print c.od1.var3 value + +Many properties can be defined at the same time on an option:: + + >>> c.cfgimpl_get_settings().extend(['unknown1', 'unknown2']) Properties can also be defined on an option group (that is, on an :term:`option description`) let's hide a group and try to access to it:: diff --git a/test/test_cache.py b/test/test_cache.py index f483c76..e1cd609 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -4,7 +4,11 @@ from tiramisu import setting setting.expires_time = 1 from tiramisu.option import IntOption, OptionDescription from tiramisu.config import Config +from tiramisu.error import ConfigError + + from time import sleep, time +from py.test import raises def make_description(): @@ -253,3 +257,25 @@ def test_reset_cache_only(): c.cfgimpl_reset_cache(only=('settings',)) assert 'u1' in values._p_.get_cached(c) assert 'u1' not in settings._p_.get_cached(c) + + +def test_force_cache(): + u1 = IntOption('u1', '', multi=True) + u2 = IntOption('u2', '') + u3 = IntOption('u3', '', multi=True) + u4 = IntOption('u4', '', properties=('disabled',)) + od = OptionDescription('od1', '', [u1, u2, u3, u4]) + c = Config(od) + c.cfgimpl_get_settings().remove('expire') + + c.cfgimpl_get_values().force_cache() + assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None), 'u4': (None, None)} + assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)} + c.read_only() + + c.cfgimpl_get_values().force_cache() + assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None)} + assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)} + + c.cfgimpl_get_settings().remove('cache') + raises(ConfigError, "c.cfgimpl_get_values().force_cache()") diff --git a/test/test_config.py b/test/test_config.py index e091294..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 +from tiramisu.error import ConflictError, ConfigError +import weakref def make_description(): @@ -131,8 +132,10 @@ def test_cfgimpl_get_home_by_path(): config.bool = False assert config.cfgimpl_get_home_by_path('gc.dummy')[1] == 'dummy' assert config.cfgimpl_get_home_by_path('dummy')[1] == 'dummy' - #assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] - #assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop'] + + +def test_not_valid_properties(): + raises(TypeError, "stroption = StrOption('str', 'Test string option', default='abc', properties=['mandatory',])") def test_information_config(): @@ -142,6 +145,7 @@ def test_information_config(): config.impl_set_information('info', string) assert config.impl_get_information('info') == string raises(ValueError, "config.impl_get_information('noinfo')") + assert config.impl_get_information('noinfo', 'default') == 'default' def test_config_impl_get_path_by_opt(): @@ -149,8 +153,10 @@ def test_config_impl_get_path_by_opt(): config = Config(descr) dummy = config.unwrap_from_path('gc.dummy') boo = config.unwrap_from_path('bool') + unknown = IntOption('test', '') assert config.cfgimpl_get_description().impl_get_path_by_opt(boo) == 'bool' assert config.cfgimpl_get_description().impl_get_path_by_opt(dummy) == 'gc.dummy' + raises(AttributeError, "config.cfgimpl_get_description().impl_get_path_by_opt(unknown)") def test_config_impl_get_opt_by_path(): @@ -160,6 +166,7 @@ def test_config_impl_get_opt_by_path(): boo = config.unwrap_from_path('bool') assert config.cfgimpl_get_description().impl_get_opt_by_path('bool') == boo assert config.cfgimpl_get_description().impl_get_opt_by_path('gc.dummy') == dummy + raises(AttributeError, "config.cfgimpl_get_description().impl_get_opt_by_path('gc.unknown')") def test_information_display(): @@ -231,8 +238,83 @@ def test_duplicated_option(): #in different OptionDescription raises(ConflictError, "config = Config(root)") + def test_cannot_assign_value_to_option_description(): descr = make_description() cfg = Config(descr) raises(TypeError, "cfg.gc = 3") + +def test_config_multi(): + i1 = IntOption('test1', '', multi=True) + i2 = IntOption('test2', '', multi=True, default_multi=1) + i3 = IntOption('test3', '', default=[2], multi=True, default_multi=1) + od = OptionDescription('test', '', [i1, i2, i3]) + config = Config(od) + assert config.test1 == [] + assert config.test2 == [] + config.test2.append() + assert config.test2 == [1] + assert config.test3 == [2] + config.test3.append() + assert config.test3 == [2, 1] + + +def test_no_validation(): + i1 = IntOption('test1', '') + od = OptionDescription('test', '', [i1]) + c = Config(od) + setting = c.cfgimpl_get_settings() + c.test1 = 1 + raises(ValueError, 'c.test1 = "yes"') + assert c.test1 == 1 + setting.remove('validator') + c.test1 = "yes" + assert c.test1 == "yes" + setting.append('validator') + raises(ValueError, 'c.test1') + del(c.test1) + assert c.test1 is None + + +def test_delete_config_with_subconfig(): + test = IntOption('test', '') + multi = IntOption('multi', '', multi=True) + od = OptionDescription('od', '', [test, multi]) + odroot = OptionDescription('odroot', '', [od]) + c = Config(odroot) + sub = c.od + val = c.cfgimpl_get_values() + setting = c.cfgimpl_get_settings() + val[test] + val[multi] + setting[test] + sub.make_dict() + del(c) + raises(ConfigError, 'val[test]') + 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 ab4b484..93f8c95 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -4,26 +4,34 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ - BoolOption, OptionDescription + BoolOption, FilenameOption, UnicodeOption, SymLinkOption, IPOption, \ + PortOption, NetworkOption, NetmaskOption, BroadcastOption, \ + DomainnameOption, OptionDescription +from tiramisu.error import PropertiesOptionError 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=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) 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, prop]) + gcgroup = OptionDescription('gc', '', [gcgroup2, gcoption, gcdummy, floatoption, prop2]) descr = OptionDescription('tiramisu', '', [gcgroup, booloption, objspaceoption, wantref_option, stroption, wantframework_option, - intoption, boolop]) + intoption, boolop, floatoption2]) return descr @@ -62,6 +70,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() @@ -96,24 +115,57 @@ def test_make_dict(): raises(ValueError, 'd2 = config.make_dict(withvalue="3")') +def test_make_dict_with_disabled(): + descr = OptionDescription("opt", "", [ + OptionDescription("s1", "", [ + BoolOption("a", "", default=False), + BoolOption("b", "", default=False, properties=('disabled',))]), + IntOption("int", "", default=42)]) + config = Config(descr) + config.read_only() + d = config.make_dict() + assert d == {"s1.a": False, "int": 42} + + 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') 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')] + 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')") + raises(ValueError, "conf.find(byname='bool', type_='unknown')") def test_find_multi(): @@ -137,3 +189,81 @@ def test_does_not_find_in_config(): descr = make_description() conf = Config(descr) raises(AttributeError, "conf.find(byname='IDontExist')") + + +def test_filename(): + a = FilenameOption('a', '') + o = OptionDescription('o', '', [a]) + c = Config(o) + c.a = u'/' + c.a = u'/tmp' + c.a = u'/tmp/' + c.a = u'/tmp/text.txt' + c.a = u'tmp' + c.a = u'tmp/' + 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')] + + +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)") + 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)") + 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 da04235..bcae92a 100644 --- a/test/test_config_domain.py +++ b/test/test_config_domain.py @@ -2,23 +2,47 @@ import autopath from py.test import raises from tiramisu.config import Config -from tiramisu.option import DomainnameOption, OptionDescription +from tiramisu.option import DomainnameOption, EmailOption, URLOption, OptionDescription def test_domainname(): d = DomainnameOption('d', '') - e = DomainnameOption('e', '', "toto.com") - od = OptionDescription('a', '', [d, e]) + f = DomainnameOption('f', '', allow_without_dot=True) + g = DomainnameOption('g', '', allow_ip=True) + od = OptionDescription('a', '', [d, f, g]) c = Config(od) c.read_write() c.d = 'toto.com' raises(ValueError, "c.d = 'toto'") c.d = 'toto3.com' - c.d = 'toto3.3la' - raises(ValueError, "c.d = '3toto.com'") - c.d = 'toto.co3' + raises(ValueError, "c.d = 'toto3.3la'") + #raises(ValueError, "c.d = '3toto.com'") + raises(ValueError, "c.d = 'toto.co3'") raises(ValueError, "c.d = 'toto_super.com'") c.d = 'toto-.com' + raises(ValueError, "c.d = 'toto..com'") + # + 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_special_domain_name(): + """domain name option that starts with a number or not + """ + d = DomainnameOption('d', '') + e = DomainnameOption('e', '', type_='netbios') + od = OptionDescription('a', '', [d,e]) + c = Config(od) + c.read_write() + c.d = '1toto.com' + c.d = '123toto.com' + c.e = 'toto' + raises(ValueError, "c.e = '1toto'") def test_domainname_netbios(): @@ -41,3 +65,35 @@ def test_domainname_hostname(): raises(ValueError, "c.d = 'toto.com'") c.d = 'toto' c.d = 'domainnametoolong' + + +def test_email(): + e = EmailOption('e', '') + od = OptionDescription('a', '', [e]) + c = Config(od) + c.read_write() + 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(): + u = URLOption('u', '') + od = OptionDescription('a', '', [u]) + c = Config(od) + c.read_write() + c.u = 'http://foo.com' + c.u = 'https://foo.com' + c.u = 'https://foo.com/' + raises(ValueError, "c.u = 'ftp://foo.com'") + raises(ValueError, "c.u = 'foo.com'") + raises(ValueError, "c.u = ':/foo.com'") + raises(ValueError, "c.u = 'foo.com/http://'") + c.u = 'https://foo.com/index.html' + c.u = 'https://foo.com/index.html?var=value&var2=val2' + raises(ValueError, "c.u = 'https://foo.com/index\\n.html'") + 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_config_ip.py b/test/test_config_ip.py index b7d3010..581cbd2 100644 --- a/test/test_config_ip.py +++ b/test/test_config_ip.py @@ -21,6 +21,11 @@ def test_ip(): c.b = '0.0.0.0' raises(ValueError, "c.b = '255.255.255.0'") + raises(ValueError, "IPOption('a', 'ip', default='192.000.023.01')") + d = IPOption('a', 'ip', default='192.0.23.1') + od = OptionDescription('od', '', [d]) + c = Config(od) + raises(ValueError, "c.a = '192.000.023.01'") def test_ip_default(): a = IPOption('a', '', '88.88.88.88') 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_mandatory.py b/test/test_mandatory.py index c535e31..5b9878a 100644 --- a/test/test_mandatory.py +++ b/test/test_mandatory.py @@ -1,7 +1,7 @@ import autopath #from py.test import raises -from tiramisu.config import Config, mandatory_warnings +from tiramisu.config import Config from tiramisu.option import StrOption, UnicodeOption, OptionDescription from tiramisu.error import PropertiesOptionError @@ -205,11 +205,11 @@ def test_mandatory_warnings_ro(): except PropertiesOptionError as err: proc = err.proptype assert proc == ['mandatory'] - assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3'] config.read_write() config.str = 'a' config.read_only() - assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3'] def test_mandatory_warnings_rw(): @@ -218,9 +218,9 @@ def test_mandatory_warnings_rw(): config.str = '' config.read_write() config.str - assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3'] config.str = 'a' - assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3'] def test_mandatory_warnings_disabled(): @@ -230,9 +230,9 @@ def test_mandatory_warnings_disabled(): setting = config.cfgimpl_get_settings() config.read_write() config.str - assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3'] setting[descr.str].append('disabled') - assert list(mandatory_warnings(config)) == ['str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3'] def test_mandatory_warnings_frozen(): @@ -242,7 +242,7 @@ def test_mandatory_warnings_frozen(): setting = config.cfgimpl_get_settings() config.read_write() config.str - assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3'] setting[descr.str].append('frozen') config.read_only() - assert list(mandatory_warnings(config)) == ['str', 'str1', 'unicode2', 'str3'] + assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3'] diff --git a/test/test_metaconfig.py b/test/test_metaconfig.py index 35d65d5..a8fc395 100644 --- a/test/test_metaconfig.py +++ b/test/test_metaconfig.py @@ -1,172 +1,203 @@ -#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, GroupConfig, MetaConfig +from tiramisu.option import IntOption, OptionDescription +from tiramisu.error import ConfigError, PropertiesOptionError -#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) + 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 -##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 +#FIXME serialization +def test_none(): + meta = make_description() + 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 + 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.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 + 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.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) + 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, '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 -# 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.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 + 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.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(): -# 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) + raises(ValueError, "GroupConfig(conf1)") + meta = GroupConfig([conf1, conf2]) + raises(ConfigError, 'meta.od1.i1') + 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 -#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' + + +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_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()") diff --git a/test/test_option.py b/test/test_option.py index 0492ed2..ec769c5 100644 --- a/test/test_option.py +++ b/test/test_option.py @@ -2,8 +2,13 @@ and to compare them """ import autopath +from py.test import raises -from tiramisu.option import BoolOption, IntOption +from tiramisu.option import IntOption, OptionDescription + + +def a_func(): + return None #def test_option_comparison(): @@ -36,3 +41,60 @@ from tiramisu.option import BoolOption, IntOption # assert dummy1 != dummy5 # assert dummy1 == dummy6 # assert dummy1 != dummy7 + + +def test_option_valid_name(): + IntOption('test', '') + raises(ValueError, 'IntOption(1, "")') + raises(ValueError, 'IntOption("impl_test", "")') + raises(ValueError, 'IntOption("_test", "")') + raises(ValueError, 'IntOption("unwrap_from_path", "")') + + +def test_option_with_callback(): + #no default value with callback + raises(ValueError, "IntOption('test', '', default=1, callback=a_func)") + + +def test_option_get_information(): + description = "it's ok" + string = 'some informations' + i = IntOption('test', description) + i.impl_set_information('info', string) + assert i.impl_get_information('info') == string + raises(ValueError, "i.impl_get_information('noinfo')") + assert i.impl_get_information('noinfo', 'default') == 'default' + assert i.impl_get_information('doc') == description + assert i.impl_getdoc() == description + + +def test_optiondescription_get_information(): + description = "it's ok" + string = 'some informations' + o = OptionDescription('test', description, []) + o.impl_set_information('info', string) + assert o.impl_get_information('info') == string + raises(ValueError, "o.impl_get_information('noinfo')") + assert o.impl_get_information('noinfo', 'default') == 'default' + assert o.impl_get_information('doc') == description + assert o.impl_getdoc() == description + + +def test_option_multi(): + IntOption('test', '', multi=True) + IntOption('test', '', multi=True, default_multi=1) + IntOption('test', '', default=[1], multi=True, default_multi=1) + #add default_multi to not multi's option + raises(ValueError, "IntOption('test', '', default_multi=1)") + #unvalid default_multi + raises(ValueError, "IntOption('test', '', multi=True, default_multi='yes')") + #not default_multi with callback + raises(ValueError, "IntOption('test', '', multi=True, default_multi=1, callback=a_func)") + + +def test_option_is_multi_by_default(): + assert IntOption('test', '').impl_is_empty_by_default() is True + assert IntOption('test', '', 1).impl_is_empty_by_default() is False + assert IntOption('test', '', multi=True).impl_is_empty_by_default() is True + assert IntOption('test', '', [1], multi=True).impl_is_empty_by_default() is False + assert IntOption('test', '', multi=True, default_multi=1).impl_is_empty_by_default() is True diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 117de9d..89f9291 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -21,7 +21,13 @@ def return_list(value=None): def return_list2(*args): - return list(args) + l = [] + for arg in args: + if isinstance(arg, list): + l.extend(arg) + else: + l.append(arg) + return l def return_value(value=None): @@ -34,6 +40,10 @@ def return_value2(*args, **kwargs): return value +def return_calc(i, j, k): + return i + j + k + + def make_description(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -93,83 +103,6 @@ def test_identical_paths(): raises(ConflictError, "make_description_duplicates()") -def make_description2(): - gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref') - gcdummy = BoolOption('dummy', 'dummy', default=False) - - floatoption = FloatOption('float', 'Test float option', default=2.3) - - objspaceoption = ChoiceOption('objspace', 'Object space', - ['std', 'thunk'], 'std') - booloption = BoolOption('bool', 'Test boolean option', default=True) - intoption = IntOption('int', 'Test int option', default=0) - stroption = StrOption('str', 'Test string option', default="abc") - # first multi - boolop = BoolOption('boolop', 'Test boolean option op', default=True) - boolop.enable_multi() - wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - # second multi - wantframework_option = BoolOption('wantframework', 'Test requires', - default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - wantframework_option.enable_multi() - - gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption]) - descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, - wantref_option, stroption, - wantframework_option, - intoption, boolop]) - return descr - - -# FIXME: il faudra tester les validations sur les multis -#def test_multi_constraints(): -# "a multi in a constraint has to have the same length" -# descr = make_description2() -# cfg = Config(descr) -# cfg.boolop = [True, True, False] -# cfg.wantframework = [False, False, True] -# -#def test_multi_raise(): -# "a multi in a constraint has to have the same length" -# # FIXME fusionner les deux tests, MAIS PROBLEME : -# # il ne devrait pas etre necessaire de refaire une config -# # si la valeur est modifiee une deuxieme fois -> -# #raises(ConflictConfigError, "cfg.wantframework = [False, False, True]") -# # ExceptionFailure: 'DID NOT RAISE' -# descr = make_description2() -# cfg = Config(descr) -# cfg.boolop = [True] -# raises(ConflictConfigError, "cfg.wantframework = [False, False, True]") -# ____________________________________________________________ -# adding dynamically new options description schema -#def test_newoption_add_in_descr(): -# descr = make_description() -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.add_child(newoption) -# config = Config(descr) -# assert config.newoption == False - -#def test_newoption_add_in_subdescr(): -# descr = make_description() -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.gc.add_child(newoption) -# config = Config(descr) -# config.bool = False -# assert config.gc.newoption == False - -#def test_newoption_add_in_config(): -# descr = make_description() -# config = Config(descr) -# config.bool = False -# newoption = BoolOption('newoption', 'dummy twoo', default=False) -# descr.add_child(newoption) -# config.cfgimpl_update() -# assert config.newoption == False -# ____________________________________________________________ - - def make_description_requires(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcdummy = BoolOption('dummy', 'dummy', default=False) @@ -310,6 +243,19 @@ def test_callback(): assert cfg.val1 == 'val' +def test_callback_params_without_callback(): + raises(ValueError, "StrOption('val2', '', callback_params={'': ('yes',)})") + + +def test_callback_invalid(): + raises(ValueError, 'val1 = StrOption("val1", "", callback="string")') + raises(ValueError, 'val1 = StrOption("val1", "", callback=return_val, callback_params="string")') + val1 = StrOption('val1', "", 'val') + raises(ValueError, "StrOption('val2', '', callback=return_value, callback_params={'': 'string'})") + raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': (('string', False),)})") + raises(ValueError, "StrOption('val4', '', callback=return_value, callback_params={'value': ((val1, 'string'),)})") + + def test_callback_value(): val1 = StrOption('val1', "", 'val') val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)}) @@ -421,12 +367,15 @@ def test_callback_multi_value(): cfg.val1.append('new-val2') assert cfg.val1 == ['new-val', 'new-val2'] assert cfg.val2 == ['new-val', 'new-val2'] - assert cfg.val4 == ['new-val', 'yes', 'new-val2', 'yes'] + assert cfg.val4 == ['new-val', 'new-val2', 'yes'] del(cfg.val1) assert cfg.val1 == ['val'] 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(): @@ -443,6 +392,14 @@ def test_callback_multi_list(): assert cfg.val1 == ['val', 'val'] +def test_callback_multi_list_extend(): + val1 = StrOption('val1', "", callback=return_list2, callback_params={'': (['1', '2', '3'], ['4', '5'])}, multi=True) + maconfig = OptionDescription('rootconfig', '', [val1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1 == ['1', '2', '3', '4', '5'] + + def test_callback_master_and_slaves_master(): val1 = StrOption('val1', "", multi=True, callback=return_val) val2 = StrOption('val2', "", multi=True) @@ -452,7 +409,7 @@ def test_callback_master_and_slaves_master(): cfg = Config(maconfig) cfg.read_write() assert cfg.val1.val1 == ['val'] - cfg.val1.val1.append(None) + cfg.val1.val1.append() assert cfg.val1.val1 == ['val', 'val'] assert cfg.val1.val2 == [None, None] @@ -467,7 +424,7 @@ def test_callback_master_and_slaves_master_list(): cfg.read_write() assert cfg.val1.val1 == ['val', 'val'] assert cfg.val1.val2 == [None, None] - cfg.val1.val1.append(None) + cfg.val1.val1.append() assert cfg.val1.val1 == ['val', 'val', None] assert cfg.val1.val2 == [None, None, None] del(cfg.val1.val1) @@ -512,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) @@ -535,7 +552,8 @@ def test_callback_master_and_slaves_value(): val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)}) val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)}) - interface1 = OptionDescription('val1', '', [val1, val2, val3, val5]) + val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6]) interface1.impl_set_group_type(groups.master) maconfig = OptionDescription('rootconfig', '', [interface1, val4]) cfg = Config(maconfig) @@ -544,30 +562,35 @@ def test_callback_master_and_slaves_value(): assert cfg.val1.val2 == [] assert cfg.val1.val3 == [] assert cfg.val1.val5 == [] + assert cfg.val1.val6 == [] # cfg.val1.val1 = ['val1'] assert cfg.val1.val1 == ['val1'] assert cfg.val1.val2 == ['val1'] assert cfg.val1.val3 == ['yes'] assert cfg.val1.val5 == ['val10'] + assert cfg.val1.val6 == ['val10'] # cfg.val1.val1.append('val2') assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11'] + assert cfg.val1.val6 == ['val10', 'val11'] # cfg.val1.val1 = ['val1', 'val2', 'val3'] assert cfg.val1.val1 == ['val1', 'val2', 'val3'] assert cfg.val1.val2 == ['val1', 'val2', 'val3'] assert cfg.val1.val3 == ['yes', 'yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11', None] + assert cfg.val1.val6 == ['val10', 'val11', None] # cfg.val1.val1.pop(2) assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11'] + assert cfg.val1.val6 == ['val10', 'val11'] # cfg.val1.val2 = ['val2', 'val2'] cfg.val1.val3 = ['val2', 'val2'] @@ -575,11 +598,13 @@ def test_callback_master_and_slaves_value(): assert cfg.val1.val2 == ['val2', 'val2'] assert cfg.val1.val3 == ['val2', 'val2'] assert cfg.val1.val5 == ['val2', 'val2'] + assert cfg.val1.val6 == ['val2', 'val2'] # cfg.val1.val1.append('val3') assert cfg.val1.val2 == ['val2', 'val2', 'val3'] assert cfg.val1.val3 == ['val2', 'val2', 'yes'] assert cfg.val1.val5 == ['val2', 'val2', None] + assert cfg.val1.val6 == ['val2', 'val2', None] cfg.cfgimpl_get_settings().remove('cache') cfg.val4 = ['val10', 'val11', 'val12'] #if value is already set, not updated ! @@ -587,6 +612,69 @@ def test_callback_master_and_slaves_value(): cfg.val1.val1.append('val3') cfg.val1.val1 = ['val1', 'val2', 'val3'] assert cfg.val1.val5 == ['val2', 'val2', 'val12'] + assert cfg.val1.val6 == ['val2', 'val2', 'val12'] + + +def test_callback_master(): + val2 = StrOption('val2', "", multi=True, callback=return_value) + val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2]) + raises(ValueError, "interface1.impl_set_group_type(groups.master)") + + +def test_callback_master_and_other_master_slave(): + val1 = StrOption('val1', "", multi=True) + val2 = StrOption('val2', "", multi=True) + val3 = StrOption('val3', "", multi=True) + val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) + val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) + val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2, val3]) + interface1.impl_set_group_type(groups.master) + interface2 = OptionDescription('val4', '', [val4, val5, val6]) + interface2.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, interface2]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == [None, None] + assert cfg.val4.val6 == [None, None] + cfg.val1.val1 = ['yes'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', None] + assert cfg.val4.val6 == [None, None] + cfg.val1.val2 = ['no'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', None] + assert cfg.val4.val6 == ['no', None] + cfg.val1.val1 = ['yes', 'yes', 'yes'] + cfg.val1.val2 = ['no', 'no', 'no'] + assert cfg.val4.val4 == ['val10', 'val11'] + assert cfg.val4.val5 == ['yes', 'yes'] + assert cfg.val4.val6 == ['no', 'no'] + + +def test_callback_different_type(): + val = IntOption('val', "", default=2) + val_ = IntOption('val_', "", default=3) + val1 = IntOption('val1', "", multi=True) + val2 = IntOption('val2', "", multi=True, callback=return_calc, callback_params={'': ((val, False), (val1, False)), 'k': ((val_, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val, val_]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1.val1 == [] + assert cfg.val1.val2 == [] + cfg.val1.val1 = [1] + assert cfg.val1.val1 == [1] + assert cfg.val1.val2 == [6] + cfg.val1.val1 = [1, 3] + assert cfg.val1.val1 == [1, 3] + assert cfg.val1.val2 == [6, 8] + cfg.val1.val1 = [1, 3, 5] + assert cfg.val1.val1 == [1, 3, 5] + assert cfg.val1.val2 == [6, 8, 10] def test_callback_hidden(): @@ -653,7 +741,7 @@ def test_callback_multi_list_params(): maconfig = OptionDescription('rootconfig', '', [val1, oval2]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val2.val2 == ['val', 'val', 'val', 'val'] + assert cfg.val2.val2 == ['val', 'val'] def test_callback_multi_list_params_key(): @@ -663,31 +751,4 @@ def test_callback_multi_list_params_key(): maconfig = OptionDescription('rootconfig', '', [val1, oval2]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val2.val2 == ['val', 'val', 'val', 'val'] - - -def test_callback_multi_multi(): - val1 = StrOption('val1', "", multi=True, default=['val1', 'val2', 'val3']) - val2 = StrOption('val2', "", multi=True, default=['val11', 'val12']) - val3 = StrOption('val3', "", default='val4') - val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val2, False))}) - val5 = StrOption('val5', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val3, False))}) - val6 = StrOption('val6', "", multi=True, default=['val21', 'val22', 'val23']) - val7 = StrOption('val7', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), (val6, False))}) - raises(ValueError, "StrOption('val8', '', multi=True, callback=return_list2, callback_params={'value': ((val1, False), (val6, False))})") - maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5, val6, val7]) - cfg = Config(maconfig) - cfg.read_write() - raises(ConfigError, "cfg.val4") - assert cfg.val5 == ['val1', 'val4', 'val2', 'val4', 'val3', 'val4'] - assert cfg.val7 == ['val1', 'val21', 'val2', 'val22', 'val3', 'val23'] - - -def test_multi_with_no_value(): - #First option return [] (so without value) - val1 = StrOption('val1', "", ['val'], multi=True) - val2 = StrOption('val2', "", multi=True) - val3 = StrOption('val3', '', multi=True, callback=return_value, callback_params={'': ((val2, False),), 'value': ((val1, False),)}) - od = OptionDescription('od', '', [val1, val2, val3]) - c = Config(od) - raises(ConfigError, "c.val3") + assert cfg.val2.val2 == ['val', 'val'] diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index d5226db..edb4f4a 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -5,7 +5,32 @@ from tiramisu.setting import owners, groups from tiramisu.config import Config from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ BroadcastOption, SymLinkOption, OptionDescription -from tiramisu.error import ConfigError +from tiramisu.error import ConfigError, ValueWarning +import warnings + + +def test_consistency(): + a = IntOption('a', '') + b = IntOption('b', '') + od = OptionDescription('od', '', [a, b]) + a.impl_add_consistency('not_equal', b) + #consistency to itself + raises(ConfigError, "a.impl_add_consistency('not_equal', a)") + #consistency with string + raises(ConfigError, "a.impl_add_consistency('not_equal', 'a')") + + +def test_consistency_warnings_only(): + a = IntOption('a', '') + b = IntOption('b', '') + od = OptionDescription('od', '', [a, b]) + a.impl_add_consistency('not_equal', b, warnings_only=True) + c = Config(od) + c.a = 1 + warnings.simplefilter("always", ValueWarning) + with warnings.catch_warnings(record=True) as w: + c.b = 1 + assert w != [] def test_consistency_not_equal(): @@ -158,6 +183,41 @@ def test_consistency_network_netmask(): raises(ValueError, "c.a = '192.168.1.1'") +def test_consistency_ip_in_network(): + a = NetworkOption('a', '') + b = NetmaskOption('b', '') + c = IPOption('c', '') + od = OptionDescription('od', '', [a, b, c]) + c.impl_add_consistency('in_network', a, b) + cfg = Config(od) + cfg.a = '192.168.1.0' + cfg.b = '255.255.255.0' + cfg.c = '192.168.1.1' + raises(ValueError, "cfg.c = '192.168.2.1'") + + +def test_consistency_ip_in_network_len_error(): + a = NetworkOption('a', '') + b = NetmaskOption('b', '') + c = IPOption('c', '') + od = OptionDescription('od', '', [a, b, c]) + c.impl_add_consistency('in_network', a) + cfg = Config(od) + raises(ConfigError, "cfg.a = '192.168.2.0'") + + +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', '') @@ -206,6 +266,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'] @@ -249,6 +311,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') @@ -272,3 +348,28 @@ 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 + + +def return_val(*args, **kwargs): + return '192.168.1.1' + + +def test_consistency_with_callback(): + a = NetworkOption('a', '', default='192.168.1.0') + b = NetmaskOption('b', '', default='255.255.255.0') + c = IPOption('c', '', callback=return_val, callback_params={'': ((a, False),)}) + od = OptionDescription('od', '', [a, b, c]) + c.impl_add_consistency('in_network', a, b) + cfg = Config(od) + cfg.c diff --git a/test/test_option_owner.py b/test/test_option_owner.py index bc96285..d4c3f6b 100644 --- a/test/test_option_owner.py +++ b/test/test_option_owner.py @@ -5,7 +5,7 @@ from tiramisu.setting import owners from tiramisu.config import Config from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ StrOption, OptionDescription -from tiramisu.error import ConfigError +from tiramisu.error import ConfigError, ConstError def make_description(): @@ -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 @@ -52,6 +53,17 @@ def test_addowner(): assert cfg.getowner(gcdummy) == owners.gen_config +def test_addowner_multiple_time(): + owners.addowner("testowner") + raises(ConstError, 'owners.addowner("testowner")') + + +def test_delete_owner(): + owners.addowner('deleted') + raises(ConstError, 'del(owners.deleted)') + raises(ValueError, 'del(owners.deleted2)') + + def test_owner_is_not_a_string(): gcdummy = BoolOption('dummy', 'dummy', default=False) descr = OptionDescription('tiramisu', '', [gcdummy]) diff --git a/test/test_option_setting.py b/test/test_option_setting.py index ed5f7d7..28327d5 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -310,6 +310,15 @@ def test_access_by_get_whith_hide(): raises(AttributeError, "c.find(byname='b1')") +def test_extend_config_properties(): + descr = make_description() + cfg = Config(descr) + setting = cfg.cfgimpl_get_settings() + assert str(setting) == str(['cache', 'expire', 'validator']) + setting.extend(['test', 'test2']) + assert str(setting) == str(['test', 'cache', 'test2', 'expire', 'validator']) + + def test_append_properties(): descr = make_description() cfg = Config(descr) @@ -367,3 +376,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/test/test_option_type.py b/test/test_option_type.py index 6f1970a..3537d6d 100644 --- a/test/test_option_type.py +++ b/test/test_option_type.py @@ -75,6 +75,16 @@ def test_group_is_hidden(): prop = err.proptype assert 'hidden' in prop +def test_extend_properties(): + descr = make_description() + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_write() + gc = config.unwrap_from_path('gc') + config.unwrap_from_path('gc.dummy') + setting[gc].extend(['hidden', 'user_defined_property']) + assert 'hidden' in setting[gc] + assert 'user_defined_property' in setting[gc] def test_group_is_hidden_multi(): descr = make_description() diff --git a/test/test_option_username.py b/test/test_option_username.py new file mode 100644 index 0000000..472e9cd --- /dev/null +++ b/test/test_option_username.py @@ -0,0 +1,25 @@ +"configuration objects global API" +import autopath +from py.test import raises + +from tiramisu.config import Config +from tiramisu.option import UsernameOption + +def test_username(): + UsernameOption('a', '', 'string') + UsernameOption('a', '', '_string') + UsernameOption('a', '', 's_tring') + UsernameOption('a', '', 'string_') + UsernameOption('a', '', 'string$') + UsernameOption('a', '', '_string$') + raises(ValueError, "UsernameOption('a', '', 'strin$g')") + UsernameOption('a', '', 's-tring') + raises(ValueError, "UsernameOption('a', '', '-string')") + UsernameOption('a', '', 's9tring') + raises(ValueError, "UsernameOption('a', '', '9string')") + raises(ValueError, "UsernameOption('a', '', '')") + UsernameOption('a', '', 's') + UsernameOption('a', '', 's2345678901234567890123456789012') + raises(ValueError, "UsernameOption('a', '', 's23456789012345678901234567890123')") + UsernameOption('a', '', 's234567890123456789012345678901$') + raises(ValueError, "UsernameOption('a', '', 's2345678901234567890123456789012$')") diff --git a/test/test_option_validator.py b/test/test_option_validator.py index 001f9f7..76ebfc8 100644 --- a/test/test_option_validator.py +++ b/test/test_option_validator.py @@ -6,6 +6,7 @@ from tiramisu.config import Config from tiramisu.option import StrOption, OptionDescription from tiramisu.setting import groups from tiramisu.error import ValueWarning +from tiramisu.i18n import _ def return_true(value, param=None): @@ -87,7 +88,7 @@ def test_validator_warning(): cfg.opt2 = 'val' assert len(w) == 1 assert w[0].message.opt == opt2 - assert str(w[0].message) == 'invalid value val for option opt2: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt2', 'error') # with warnings.catch_warnings(record=True) as w: cfg.opt3.append('val') @@ -97,7 +98,7 @@ def test_validator_warning(): cfg.opt3.append('val1') assert len(w) == 1 assert w[0].message.opt == opt3 - assert str(w[0].message) == 'invalid value val1 for option opt3: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt3', 'error') raises(ValueError, "cfg.opt2 = 1") # with warnings.catch_warnings(record=True) as w: @@ -105,9 +106,9 @@ def test_validator_warning(): cfg.opt3.append('val') assert len(w) == 2 assert w[0].message.opt == opt2 - assert str(w[0].message) == 'invalid value val for option opt2: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('opt2', 'error') assert w[1].message.opt == opt3 - assert str(w[1].message) == 'invalid value val1 for option opt3: error' + assert str(w[1].message) == _("warning on the value of the option {0}: {1}").format('opt3', 'error') def test_validator_warning_master_slave(): @@ -127,29 +128,29 @@ def test_validator_warning_master_slave(): cfg.ip_admin_eth0.netmask_admin_eth0 = ['val1'] assert len(w) == 1 assert w[0].message.opt == netmask_admin_eth0 - assert str(w[0].message) == 'invalid value val1 for option netmask_admin_eth0: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('netmask_admin_eth0', 'error') # with warnings.catch_warnings(record=True) as w: cfg.ip_admin_eth0.ip_admin_eth0 = ['val'] assert len(w) == 1 assert w[0].message.opt == ip_admin_eth0 - assert str(w[0].message) == 'invalid value val for option ip_admin_eth0: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error') # with warnings.catch_warnings(record=True) as w: cfg.ip_admin_eth0.ip_admin_eth0 = ['val', 'val1', 'val1'] assert len(w) == 1 assert w[0].message.opt == ip_admin_eth0 - assert str(w[0].message) == 'invalid value val for option ip_admin_eth0: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error') # with warnings.catch_warnings(record=True) as w: cfg.ip_admin_eth0.ip_admin_eth0 = ['val1', 'val', 'val1'] assert len(w) == 1 assert w[0].message.opt == ip_admin_eth0 - assert str(w[0].message) == 'invalid value val for option ip_admin_eth0: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error') # warnings.resetwarnings() with warnings.catch_warnings(record=True) as w: cfg.ip_admin_eth0.ip_admin_eth0 = ['val1', 'val1', 'val'] assert len(w) == 1 assert w[0].message.opt == ip_admin_eth0 - assert str(w[0].message) == 'invalid value val for option ip_admin_eth0: error' + assert str(w[0].message) == _("warning on the value of the option {0}: {1}").format('ip_admin_eth0', 'error') 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/test/test_permissive.py b/test/test_permissive.py index 38a8bcf..615fef7 100644 --- a/test/test_permissive.py +++ b/test/test_permissive.py @@ -9,7 +9,8 @@ from tiramisu.error import PropertiesOptionError def make_description(): u1 = IntOption('u1', '', properties=('frozen', 'mandatory', 'disabled', )) - return OptionDescription('od1', '', [u1]) + u2 = IntOption('u2', '', properties=('frozen', 'mandatory', 'disabled', )) + return OptionDescription('od1', '', [u1, u2]) def test_permissive(): @@ -91,3 +92,108 @@ def test_invalid_permissive(): setting = config.cfgimpl_get_settings() config.read_write() raises(TypeError, "setting.setpermissive(['frozen', 'disabled',])") + + +def test_permissive_option(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_write() + + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.setpermissive(('disabled',), u1) + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == [] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.append('permissive') + config.u1 + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + setting.remove('permissive') + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert props == [] + props = [] + try: + config.u2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + +def test_permissive_option_mandatory(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_only() + props = [] + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['disabled', 'mandatory']) + setting.setpermissive(('mandatory', 'disabled',), u1) + setting.append('permissive') + config.u1 + setting.remove('permissive') + try: + config.u1 + except PropertiesOptionError as err: + props = err.proptype + assert set(props) == set(['disabled', 'mandatory']) + + +def test_permissive_option_frozen(): + descr = make_description() + config = Config(descr) + u1 = descr.u1 + setting = config.cfgimpl_get_settings() + config.read_write() + setting.setpermissive(('frozen', 'disabled'), u1) + config.u1 = 1 + assert config.u1 == 1 + setting.append('permissive') + assert config.u1 == 1 + setting.remove('permissive') + assert config.u1 == 1 + + +def test_invalid_option_permissive(): + descr = make_description() + u1 = descr.u1 + config = Config(descr) + setting = config.cfgimpl_get_settings() + config.read_write() + raises(TypeError, "setting.setpermissive(['frozen', 'disabled',], u1)") diff --git a/test/test_requires.py b/test/test_requires.py index ba6cf3f..ff9c2a6 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -24,6 +24,42 @@ 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(): + a = BoolOption('activate_service', '', True) + raises(ValueError, "IPOption('ip_address_service', '', requires='string')") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'unknown': True}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'expected': False, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'inverse': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'transitive': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'same_action': 'string'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': 'string', 'expected': False, 'action': 'disabled'}])") + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': 'string', 'action': 'disabled'}])") def test_requires_same_action(): @@ -504,6 +540,11 @@ def test_requires_requirement_append(): c.cfgimpl_get_settings()[b].append("test") +def test_requires_different_inverse(): + a = BoolOption('activate_service', '', True) + raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': True, 'action': 'disabled', 'inverse': True}, {'option': a, 'expected': True, 'action': 'disabled', 'inverse': False}])") + + def test_requires_recursive_path(): a = BoolOption('activate_service', '', True) b = IPOption('ip_address_service', '', diff --git a/test/test_slots.py b/test/test_slots.py index 1f2aee6..2031cf8 100644 --- a/test/test_slots.py +++ b/test/test_slots.py @@ -3,9 +3,10 @@ import autopath from py.test import raises from tiramisu.config import Config, SubConfig -from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ +from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption,\ StrOption, SymLinkOption, UnicodeOption, IPOption, OptionDescription, \ - PortOption, NetworkOption, NetmaskOption, DomainnameOption + PortOption, NetworkOption, NetmaskOption, DomainnameOption, EmailOption, \ + URLOption, FilenameOption def test_slots_option(): @@ -35,6 +36,12 @@ def test_slots_option(): raises(AttributeError, "c.x = 1") c = DomainnameOption('a', '') raises(AttributeError, "c.x = 1") + c = EmailOption('a', '') + raises(AttributeError, "c.x = 1") + c = URLOption('a', '') + raises(AttributeError, "c.x = 1") + c = FilenameOption('a', '') + raises(AttributeError, "c.x = 1") def test_slots_option_readonly(): @@ -49,7 +56,10 @@ def test_slots_option_readonly(): j = NetworkOption('j', '') k = NetmaskOption('k', '') l = DomainnameOption('l', '') - m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l]) + o = EmailOption('o', '') + p = URLOption('p', '') + q = FilenameOption('q', '') + m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l, o, p, q]) a._requires = 'a' b._requires = 'b' c._requires = 'c' @@ -62,6 +72,9 @@ def test_slots_option_readonly(): k._requires = 'k' l._requires = 'l' m._requires = 'm' + o._requires = 'o' + p._requires = 'p' + q._requires = 'q' Config(m) raises(AttributeError, "a._requires = 'a'") raises(AttributeError, "b._requires = 'b'") @@ -75,6 +88,9 @@ def test_slots_option_readonly(): raises(AttributeError, "k._requires = 'k'") raises(AttributeError, "l._requires = 'l'") raises(AttributeError, "m._requires = 'm'") + raises(AttributeError, "o._requires = 'o'") + raises(AttributeError, "p._requires = 'p'") + raises(AttributeError, "q._requires = 'q'") def test_slots_option_readonly_name(): @@ -90,7 +106,10 @@ def test_slots_option_readonly_name(): j = NetworkOption('j', '') k = NetmaskOption('k', '') l = DomainnameOption('l', '') - m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l]) + o = DomainnameOption('o', '') + p = DomainnameOption('p', '') + q = DomainnameOption('q', '') + m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l, o, p, q]) raises(AttributeError, "a._name = 'a'") raises(AttributeError, "b._name = 'b'") raises(AttributeError, "c._name = 'c'") @@ -104,6 +123,9 @@ def test_slots_option_readonly_name(): raises(AttributeError, "k._name = 'k'") raises(AttributeError, "l._name = 'l'") raises(AttributeError, "m._name = 'm'") + raises(AttributeError, "o._name = 'o'") + raises(AttributeError, "p._name = 'p'") + raises(AttributeError, "q._name = 'q'") def test_slots_description(): diff --git a/test/test_state.py b/test/test_state.py index ef46ce2..03be06e 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -1,10 +1,13 @@ +import autopath + 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 from pickle import dumps, loads +from py.test import raises def return_value(value=None): @@ -90,10 +93,48 @@ 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}]) - #u.impl_add_consistency('not_equal', b) s = SymLinkOption('s', u) o = OptionDescription('o', '', [b, u, s]) o1 = OptionDescription('o1', '', [o]) @@ -107,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}]) @@ -169,10 +215,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 +234,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 +252,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 +275,94 @@ 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 + raises(ConfigError, "dumps(meta)") + 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 + + +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 eb5c6ef..0b97644 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -19,15 +19,16 @@ # ____________________________________________________________ "enables us to carry out a calculation and return an option's value" from tiramisu.error import PropertiesOptionError, ConfigError +from tiramisu.setting import multitypes from tiramisu.i18n import _ # ____________________________________________________________ -def carry_out_calculation(name, config, callback, callback_params, +def carry_out_calculation(option, config, callback, callback_params, index=None, max_len=None): """a function that carries out a calculation for an option's value - :param name: the option name (`opt._name`) + :param name: the option :param config: the context config in order to have the whole options available :param callback: the name of the callback function @@ -48,13 +49,13 @@ def carry_out_calculation(name, config, callback, callback_params, Values could have multiple values only when key is ''. * if no callback_params: - => calculate() + => calculate(, [], {}) * if callback_params={'': ('yes',)} - => calculate('yes') + => calculate(, ['yes'], {}) * if callback_params={'value': ('yes',)} - => calculate(value='yes') + => calculate(, [], {'value': 'yes'}) * if callback_params={'': ('yes', 'no')} => calculate('yes', 'no') @@ -62,58 +63,71 @@ def carry_out_calculation(name, config, callback, callback_params, * if callback_params={'value': ('yes', 'no')} => ValueError() + * if callback_params={'': (['yes', 'no'],)} + => calculate(, ['yes', 'no'], {}) + + * if callback_params={'value': ('yes', 'no')} + => raises ValueError() + * if callback_params={'': ((opt1, False),)} - a simple option: opt1 == 11 - => calculate(11) + => calculate(, [11], {}) - - a multi option: + - a multi option and not master/slave: opt1 == [1, 2, 3] - => calculate(1) - => calculate(2) - => calculate(3) + => calculate(, [[1, 2, 3]], {}) + + - option is master or slave of opt1: + opt1 == [1, 2, 3] + => calculate(, [1], {}) + => calculate(, [2], {}) + => calculate(, [3], {}) + + - opt is a master or slave but not related to option: + opt1 == [1, 2, 3] + => calculate(, [[1, 2, 3]], {}) * if callback_params={'value': ((opt1, False),)} - a simple option: opt1 == 11 - => calculate(value=11) + => calculate(, [], {'value': 11}) - a multi option: opt1 == [1, 2, 3] - => calculate(value=1) - => calculate(value=2) - => calculate(value=3) + => calculate(, [], {'value': [1, 2, 3]}) * if callback_params={'': ((opt1, False), (opt2, False))} + - two single options + opt1 = 11 + opt2 = 12 + => calculate(, [11, 12], {}) + - a multi option with a simple option opt1 == [1, 2, 3] - opt2 == 11 - => calculate(1, 11) - => calculate(2, 11) - => calculate(3, 11) + opt2 == 12 + => calculate(, [[1, 2, 3], 12], {}) - a multi option with an other multi option but with same length opt1 == [1, 2, 3] opt2 == [11, 12, 13] - => calculate(1, 11) - => calculate(2, 12) - => calculate(3, 13) + => calculate(, [[1, 2, 3], [11, 12, 13]], {}) - a multi option with an other multi option but with different length opt1 == [1, 2, 3] opt2 == [11, 12] - => ConfigError() + => calculate(, [[1, 2, 3], [11, 12]], {}) - a multi option without value with a simple option opt1 == [] opt2 == 11 - => [] + => calculate(, [[], 12], {}) * if callback_params={'value': ((opt1, False), (opt2, False))} - => ConfigError() + => raises ValueError() If index is not None, return a value, otherwise return: @@ -132,29 +146,36 @@ def carry_out_calculation(name, config, callback, callback_params, for callbk in callbacks: if isinstance(callbk, tuple): # callbk is something link (opt, True|False) - option, force_permissive = callbk + opt, force_permissive = callbk path = config.cfgimpl_get_description().impl_get_path_by_opt( - option) + 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 raise ConfigError(_('unable to carry out a calculation, ' 'option {0} has properties: {1} for: ' - '{2}').format(option._name, + '{2}').format(opt._name, err.proptype, - name)) - is_multi = option.impl_is_multi() + option._name)) + + is_multi = False + if opt.impl_is_multi(): + #opt is master, search if option is a slave + if opt.impl_get_multitype() == multitypes.master: + if option in opt.impl_get_master_slaves(): + is_multi = True + #opt is slave, search if option is an other slaves + elif opt.impl_get_multitype() == multitypes.slave: + if option in opt.impl_get_master_slaves().impl_get_master_slaves(): + is_multi = True if is_multi: - len_value = len(value) - if len_multi is not None and len_multi != len_value: - raise ConfigError(_('unable to carry out a ' - 'calculation, option value with' - ' multi types must have same ' - 'length for: {0}').format(name)) - len_multi = len_value + len_multi = len(value) one_is_multi = True tcparams.setdefault(key, []).append((value, is_multi)) else: @@ -167,16 +188,9 @@ def carry_out_calculation(name, config, callback, callback_params, if one_is_multi: ret = [] if index: - if index < len_multi: - range_ = [index] - else: - range_ = [] - ret = None + range_ = [index] else: - if max_len and max_len < len_multi: - range_ = range(max_len) - else: - range_ = range(len_multi) + range_ = range(len_multi) for incr in range_: args = [] kwargs = {} @@ -195,10 +209,7 @@ def carry_out_calculation(name, config, callback, callback_params, if index: ret = calc else: - if isinstance(calc, list): - ret.extend(calc) - else: - ret.append(calc) + ret.append(calc) return ret else: # no value is multi @@ -212,7 +223,18 @@ def carry_out_calculation(name, config, callback, callback_params, args.append(couple[0]) else: kwargs[key] = couple[0] - return calculate(callback, args, kwargs) + ret = calculate(callback, args, kwargs) + if callback_params != {}: + if isinstance(ret, list) and max_len: + ret = ret[:max_len] + if len(ret) < max_len: + ret = ret + [None] * (max_len - len(ret)) + if isinstance(ret, list) and index: + if len(ret) < index + 1: + ret = None + else: + ret = ret[index] + return ret def calculate(callback, args, kwargs): diff --git a/tiramisu/config.py b/tiramisu/config.py index 1c05e22..2a882a1 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -47,7 +47,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 @@ -148,19 +148,25 @@ class SubConfig(object): except UnicodeEncodeError: lines.append("{0} = {1}".format(name, value.encode(default_encoding))) - except PropertiesOptionError: - pass return '\n'.join(lines) __repr__ = __str__ def _cfgimpl_get_context(self): - return self._impl_context() + """context could be None, we need to test it + context is None only if all reference to `Config` object is deleted + (for example we delete a `Config` and we manipulate a reference to + old `SubConfig`, `Values`, `Multi` or `Settings`) + """ + context = self._impl_context() + if context is None: + raise ConfigError(_('the context does not exist anymore')) + return context 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 @@ -249,7 +255,8 @@ class SubConfig(object): force_properties=force_properties, force_permissive=force_permissive) - def find(self, bytype=None, byname=None, byvalue=None, type_='option'): + def find(self, bytype=None, byname=None, byvalue=None, type_='option', + check_properties=True): """ finds a list of options recursively in the config @@ -261,11 +268,11 @@ class SubConfig(object): return self._cfgimpl_get_context()._find(bytype, byname, byvalue, first=False, type_=type_, - _subpath=self.cfgimpl_get_path() - ) + _subpath=self.cfgimpl_get_path(), + check_properties=check_properties) def find_first(self, bytype=None, byname=None, byvalue=None, - type_='option', display_error=True): + type_='option', display_error=True, check_properties=True): """ finds an option recursively in the config @@ -276,7 +283,8 @@ class SubConfig(object): """ return self._cfgimpl_get_context()._find( bytype, byname, byvalue, first=True, type_=type_, - _subpath=self.cfgimpl_get_path(), display_error=display_error) + _subpath=self.cfgimpl_get_path(), display_error=display_error, + check_properties=check_properties) def _find(self, bytype, byname, byvalue, first, type_='option', _subpath=None, check_properties=True, display_error=True): @@ -287,12 +295,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(): @@ -442,23 +447,20 @@ class SubConfig(object): return pathsvalues def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten): - if isinstance(opt, OptionDescription): - try: + try: + if isinstance(opt, OptionDescription): pathsvalues += getattr(self, path).make_dict(flatten, _currpath + path.split('.')) - except PropertiesOptionError: - pass # this just a hidden or disabled option - else: - try: + else: 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 + except PropertiesOptionError: + pass def cfgimpl_get_path(self): descr = self.cfgimpl_get_description() @@ -466,9 +468,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() @@ -507,7 +509,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): @@ -525,11 +528,48 @@ class CommonConfig(SubConfig): """ return self._impl_values.get_information(key, default) + # ----- state + def __getstate__(self): + if self._impl_meta is not None: + raise ConfigError(_('cannot serialize Config with MetaConfig')) + slots = set() + for subclass in self.__class__.__mro__: + if subclass is not object: + slots.update(subclass.__slots__) + slots -= frozenset(['_impl_context', '_impl_meta', '__weakref__']) + state = {} + for slot in slots: + try: + state[slot] = getattr(self, slot) + except AttributeError: + pass + storage = self._impl_values._p_._storage + if not storage.serializable: + 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() + return state + + def __setstate__(self, state): + for key, value in state.items(): + if key not in ['_storage', '_impl_setting']: + setattr(self, key, value) + set_storage(**state['_impl_setting']) + self._impl_context = weakref.ref(self) + self._impl_settings.context = weakref.ref(self) + self._impl_values.context = weakref.ref(self) + storage = get_storage(test=self._impl_test, **state['_storage']) + self._impl_values._impl_setstate(storage) + self._impl_settings._impl_setstate(storage) + self._impl_meta = None + # ____________________________________________________________ -class Config(CommonConfig): +class Config(_CommonConfig): "main configuration management entry" - __slots__ = ('__weakref__', '_impl_test') + __slots__ = ('__weakref__',) def __init__(self, descr, session_id=None, persistent=False): """ Configuration option management master class @@ -553,41 +593,6 @@ class Config(CommonConfig): #undocumented option used only in test script self._impl_test = False - def __getstate__(self): - if self._impl_meta is not None: - raise ConfigError('cannot serialize Config with meta') - slots = set() - for subclass in self.__class__.__mro__: - if subclass is not object: - slots.update(subclass.__slots__) - slots -= frozenset(['_impl_context', '__weakref__']) - state = {} - for slot in slots: - try: - state[slot] = getattr(self, slot) - except AttributeError: - pass - storage = self._impl_values._p_._storage - if not storage.serializable: - 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() - return state - - def __setstate__(self, state): - for key, value in state.items(): - if key not in ['_storage', '_impl_setting']: - setattr(self, key, value) - set_storage(**state['_impl_setting']) - self._impl_context = weakref.ref(self) - self._impl_settings.context = weakref.ref(self) - self._impl_values.context = weakref.ref(self) - storage = get_storage(test=self._impl_test, **state['_storage']) - self._impl_values._impl_setstate(storage) - self._impl_settings._impl_setstate(storage) - def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')): @@ -597,115 +602,121 @@ class Config(CommonConfig): self.cfgimpl_get_settings().reset_cache(only_expired=only_expired) -#class MetaConfig(CommonConfig): -# __slots__ = ('_impl_children',) +class GroupConfig(_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, session_id=None, persistent=False, + _descr=None): + if not isinstance(children, list): + raise ValueError(_("metaconfig's children must be a list")) + 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(GroupConfig, self).__init__(_descr, weakref.ref(self)) + self._impl_meta = None + #undocumented option used only in test script + self._impl_test = False -# 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 + 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 which 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_get_context(self): -# "a meta config is a config wich has a setting, that is itself" -# return self + 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, GroupConfig): + setattr(child, path, value) + else: + child.setattrs(path, value) + except PropertiesOptionError: + pass -# 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 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 \ + isinstance(self, MetaConfig): + bypath = self._find(bytype=None, byvalue=None, byname=byname, + first=True, type_='path', + check_properties=False, + display_error=display_error) + 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 isinstance(value, Multi): + if byvalue in value: + ret.append(child) + else: + if value == byvalue: + ret.append(child) + else: + ret.append(child) + else: + ret.append(child.find_first(byname=byname, + byvalue=byvalue, + type_=type_, + display_error=False)) + else: + 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) -# 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) +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 - - :returns: generator of mandatory Option's path - - """ - #if value in cache, properties are not calculated - config.cfgimpl_reset_cache(only=('values',)) - for path in config.cfgimpl_get_description().impl_getpaths( - include_groups=True): - try: - config._getattr(path, force_properties=frozenset(('mandatory',))) - except PropertiesOptionError as err: - if err.proptype == ['mandatory']: - yield path - config.cfgimpl_reset_cache(only=('values',)) + #only for retro-compatibility + config.cfgimpl_get_values().mandatory_warnings() diff --git a/tiramisu/option.py b/tiramisu/option.py index ca143a0..fee61b5 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -39,9 +39,7 @@ forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', def valid_name(name): "an option's name is a str and does not start with 'impl' or 'cfgimpl'" - try: - name = str(name) - except: + if not isinstance(name, str): return False if re.match(name_regexp, name) is None and not name.startswith('_') \ and name not in forbidden_names \ @@ -337,7 +335,7 @@ class Option(BaseOption): self._consistencies = None def _launch_consistency(self, func, option, value, context, index, - all_cons_opts): + all_cons_opts, warnings_only): """Launch consistency now :param func: function name, this name should start with _cons_ @@ -352,12 +350,11 @@ class Option(BaseOption): :type index: `int` :param all_cons_opts: all options concerne by this consistency :type all_cons_opts: `list` of `tiramisu.option.Option` + :param warnings_only: specific raise error for warning + :type warnings_only: `boolean` """ 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: @@ -368,7 +365,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() @@ -382,7 +380,7 @@ class Option(BaseOption): except IndexError: #so return if no value return - getattr(self, func)(all_cons_opts, all_cons_vals) + getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only) def impl_validate(self, value, context=None, validate=True, force_index=None): @@ -412,7 +410,7 @@ class Option(BaseOption): else: validator_params = {'': (val,)} # Raise ValueError if not valid - carry_out_calculation(self._name, config=context, + carry_out_calculation(self, config=context, callback=self._validator[0], callback_params=validator_params) @@ -420,23 +418,41 @@ class Option(BaseOption): if _value is None: return # option validation - self._validate(_value) + try: + self._validate(_value) + except ValueError as err: + raise ValueError(_('invalid value for option {0}: {1}' + '').format(self._name, err)) + error = None + warning = None try: # valid with self._validator val_validator(_value) - # if not context launch consistency validation - if context is not None: - descr._valid_consistency(self, _value, context, _index) - self._second_level_validation(_value) - except ValueError as err: - msg = _("invalid value {0} for option {1}: {2}").format( - _value, self._name, err) + self._second_level_validation(_value, self._warnings_only) + except ValueError as error: if self._warnings_only: - warnings.warn_explicit(ValueWarning(msg, self), - ValueWarning, - self.__class__.__name__, 0) - else: - raise ValueError(msg) + warning = error + error = None + except ValueWarning as warning: + pass + if error is None and warning is None: + try: + # if context launch consistency validation + if context is not None: + descr._valid_consistency(self, _value, context, _index) + except ValueError as error: + pass + except ValueWarning as warning: + pass + if warning: + msg = _("warning on the value of the option {0}: {1}").format( + self._name, warning) + warnings.warn_explicit(ValueWarning(msg, self), + ValueWarning, + self.__class__.__name__, 0) + elif error: + raise ValueError(_("invalid value for option {0}: {1}").format( + self._name, error)) # generic calculation if context is not None: @@ -446,17 +462,13 @@ class Option(BaseOption): do_validation(value, force_index) else: if not isinstance(value, list): - raise ValueError(_("which must be a list").format(value, - self._name)) + raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name)) for index, val in enumerate(value): do_validation(val, index) - def impl_getdefault(self, default_multi=False): + def impl_getdefault(self): "accessing the default value" - if not default_multi or not self.impl_is_multi(): - return self._default - else: - return self.getdefault_multi() + return self._default def impl_getdefault_multi(self): "accessing the default value for a multi" @@ -493,7 +505,7 @@ class Option(BaseOption): def impl_is_multi(self): return self._multi - def impl_add_consistency(self, func, *other_opts): + def impl_add_consistency(self, func, *other_opts, **params): """Add consistency means that value will be validate with other_opts option's values. @@ -501,16 +513,18 @@ class Option(BaseOption): :type func: `str` :param other_opts: options used to validate value :type other_opts: `list` of `tiramisu.option.Option` + :param params: extra params (only warnings_only are allowed) """ if self._consistencies is None: self._consistencies = [] + warnings_only = params.get('warnings_only', False) for opt in other_opts: if not isinstance(opt, Option): - raise ConfigError(_('consistency should be set with an option')) + raise ConfigError(_('consistency must be set with an option')) if self is opt: raise ConfigError(_('cannot add consistency with itself')) if self.impl_is_multi() != opt.impl_is_multi(): - raise ConfigError(_('every options in consistency should be ' + raise ConfigError(_('every options in consistency must be ' 'multi or none')) func = '_cons_{0}'.format(func) all_cons_opts = tuple([self] + list(other_opts)) @@ -519,19 +533,23 @@ class Option(BaseOption): if self.impl_is_multi(): for idx, val in enumerate(value): self._launch_consistency(func, self, val, None, - idx, all_cons_opts) + idx, all_cons_opts, warnings_only) else: self._launch_consistency(func, self, value, None, - None, all_cons_opts) - self._consistencies.append((func, all_cons_opts)) + None, all_cons_opts, warnings_only) + self._consistencies.append((func, all_cons_opts, params)) self.impl_validate(self.impl_getdefault()) - def _cons_not_equal(self, opts, vals): + def _cons_not_equal(self, opts, vals, warnings_only): for idx_inf, val_inf in enumerate(vals): for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]): if val_inf == val_sup is not None: - raise ValueError(_("same value for {0} and {1}").format( - opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name)) + if warnings_only: + msg = _("same value for {0} and {1}, should be different") + else: + msg = _("same value for {0} and {1}, must be different") + raise ValueError(msg.format(opts[idx_inf]._name, + opts[idx_inf + idx_sup + 1]._name)) def _impl_convert_callbacks(self, descr, load=False): if not load and self._callback is None: @@ -587,38 +605,22 @@ 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), consistency[2])) if load: del(self._state_consistencies) self._consistencies = new_value else: self._state_consistencies = new_value - def _second_level_validation(self, value): + def _second_level_validation(self, value, warnings_only): pass @@ -663,7 +665,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)) @@ -676,7 +678,7 @@ class BoolOption(Option): def _validate(self, value): if not isinstance(value, bool): - raise ValueError(_('value must be a boolean')) + raise ValueError(_('invalid boolean')) class IntOption(Option): @@ -686,7 +688,7 @@ class IntOption(Option): def _validate(self, value): if not isinstance(value, int): - raise ValueError(_('value must be an integer')) + raise ValueError(_('invalid integer')) class FloatOption(Option): @@ -696,7 +698,7 @@ class FloatOption(Option): def _validate(self, value): if not isinstance(value, float): - raise ValueError(_('value must be a float')) + raise ValueError(_('invalid float')) class StrOption(Option): @@ -706,12 +708,11 @@ class StrOption(Option): def _validate(self, value): if not isinstance(value, str): - raise ValueError(_('value must be a string, not ' - '{0}').format(type(value))) + raise ValueError(_('invalid string')) if sys.version_info[0] >= 3: - #UnicodeOption is same has StrOption in python 3+ + #UnicodeOption is same as StrOption in python 3+ class UnicodeOption(StrOption): __slots__ = tuple() pass @@ -724,7 +725,7 @@ else: def _validate(self, value): if not isinstance(value, unicode): - raise ValueError(_('value must be an unicode')) + raise ValueError(_('invalid unicode')) class SymLinkOption(BaseOption): @@ -782,17 +783,51 @@ class IPOption(Option): warnings_only=warnings_only) 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 + 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)) except ValueError: - raise ValueError(_('invalid IP {0}').format(self._name)) + raise ValueError(_('invalid IP')) - def _second_level_validation(self, value): + def _second_level_validation(self, value, warnings_only): ip = IP('{0}/32'.format(value)) if not self._allow_reserved and ip.iptype() == 'RESERVED': - raise ValueError(_("IP mustn't not be in reserved class")) + if warnings_only: + msg = _("IP shouldn't be in reserved class") + else: + msg = _("invalid IP, mustn't be in reserved class") + raise ValueError(msg) if self._private_only and not ip.iptype() == 'PRIVATE': - raise ValueError(_("IP must be in private class")) + if warnings_only: + msg = _("IP should be in private class") + else: + msg = _("invalid IP, must be in private class") + raise ValueError(msg) + + def _cons_in_network(self, opts, vals, warnings_only): + if len(vals) != 3: + raise ConfigError(_('invalid len for vals')) + if None in vals: + return + ip, network, netmask = vals + if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): + if warnings_only: + msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}' + ' ({5})') + else: + msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with ' + 'netmask {4} ({5})') + raise ValueError(msg.format(ip, opts[0]._name, network, + opts[1]._name, netmask, opts[2]._name)) class PortOption(Option): @@ -852,17 +887,23 @@ class PortOption(Option): if self._allow_range and ":" in str(value): value = str(value).split(':') if len(value) != 2: - raise ValueError('range must have two values only') + raise ValueError(_('invalid port, range must have two values ' + 'only')) if not value[0] < value[1]: - raise ValueError('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: + try: + int(val) + except ValueError: + raise ValueError(_('invalid port')) if not self._min_value <= int(val) <= self._max_value: - raise ValueError('port must be an between {0} and {1}' - ''.format(self._min_value, self._max_value)) + raise ValueError(_('invalid port, must be an between {0} ' + 'and {1}').format(self._min_value, + self._max_value)) class NetworkOption(Option): @@ -874,12 +915,16 @@ class NetworkOption(Option): try: IP(value) except ValueError: - raise ValueError(_('invalid network address {0}').format(self._name)) + raise ValueError(_('invalid network address')) - def _second_level_validation(self, value): + def _second_level_validation(self, value, warnings_only): ip = IP(value) if ip.iptype() == 'RESERVED': - raise ValueError(_("network shall not be in reserved class")) + if warnings_only: + msg = _("network address shouldn't be in reserved class") + else: + msg = _("invalid network address, mustn't be in reserved class") + raise ValueError(msg) class NetmaskOption(Option): @@ -891,21 +936,22 @@ class NetmaskOption(Option): try: IP('0.0.0.0/{0}'.format(value)) except ValueError: - raise ValueError(_('invalid netmask address {0}').format(self._name)) + raise ValueError(_('invalid netmask address')) - def _cons_network_netmask(self, opts, vals): + def _cons_network_netmask(self, opts, vals, warnings_only): #opts must be (netmask, network) options if None in vals: return - self.__cons_netmask(opts, vals[0], vals[1], False) + self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only) - def _cons_ip_netmask(self, opts, vals): + def _cons_ip_netmask(self, opts, vals, warnings_only): #opts must be (netmask, ip) options if None in vals: return - self.__cons_netmask(opts, vals[0], vals[1], True) + self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only) - def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net): + def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, + warnings_only): if len(opts) != 2: raise ConfigError(_('invalid len for opts')) msg = None @@ -918,23 +964,18 @@ 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} ({3})," - " this network is an IP") + pass else: if make_net: - msg = _("invalid IP {0} ({1}) with netmask {2} ({3})," + 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} ({3})") - else: - msg = _("invalid network {0} ({1}) with netmask {2} ({3})") + 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, - val_netmask, self._name)) + val_netmask)) class BroadcastOption(Option): @@ -945,9 +986,9 @@ class BroadcastOption(Option): try: IP('{0}/32'.format(value)) except ValueError: - raise ValueError(_('invalid broadcast address {0}').format(self._name)) + raise ValueError(_('invalid broadcast address')) - def _cons_broadcast(self, opts, vals): + def _cons_broadcast(self, opts, vals, warnings_only): if len(vals) != 3: raise ConfigError(_('invalid len for vals')) if None in vals: @@ -967,20 +1008,44 @@ class DomainnameOption(Option): domainname: fqdn: with tld, not supported yet """ - __slots__ = ('_type', '_allow_ip') + __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re') _opt_type = 'domainname' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, allow_ip=False, type_='domainname', - warnings_only=False): + warnings_only=False, allow_without_dot=False): if type_ not in ['netbios', 'hostname', 'domainname']: raise ValueError(_('unknown type_ {0} for hostname').format(type_)) self._type = type_ if allow_ip not in [True, False]: raise ValueError(_('allow_ip must be a boolean')) + if allow_without_dot not in [True, False]: + raise ValueError(_('allow_without_dot must be a boolean')) self._allow_ip = allow_ip + self._allow_without_dot = allow_without_dot + end = '' + extrachar = '' + extrachar_mandatory = '' + if self._type != 'netbios': + allow_number = '\d' + else: + allow_number = '' + if self._type == 'netbios': + length = 14 + elif self._type == 'hostname': + length = 62 + elif self._type == 'domainname': + length = 62 + if allow_without_dot is False: + extrachar_mandatory = '\.' + else: + extrachar = '\.' + end = '+[a-z]*' + self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$' + ''.format(allow_number, extrachar, length, + extrachar_mandatory, end)) super(DomainnameOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, @@ -999,29 +1064,94 @@ class DomainnameOption(Option): return except ValueError: pass - if self._type == 'netbios': - length = 15 - extrachar = '' - elif self._type == 'hostname': - length = 63 - extrachar = '' - elif self._type == 'domainname': - length = 255 - extrachar = '\.' - if '.' not in value: - raise ValueError(_("invalid value for {0}, must have dot" - "").format(self._name)) - if len(value) > length: - raise ValueError(_("invalid domainname's length for" - " {0} (max {1})").format(self._name, length)) - if len(value) == 1: - raise ValueError(_("invalid domainname's length for {0} (min 2)" - "").format(self._name)) - regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar) - if re.match(regexp, value) is None: + 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) < 2: + raise ValueError(_("invalid domainname's length (min 2)")) + if not self._domain_re.search(value): raise ValueError(_('invalid domainname')) +class EmailOption(DomainnameOption): + __slots__ = tuple() + _opt_type = 'email' + username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") + + def _validate(self, value): + splitted = value.split('@', 1) + try: + username, domain = splitted + except ValueError: + raise ValueError(_('invalid email address, must contains one @' + )) + if not self.username_re.search(username): + raise ValueError(_('invalid username in email address')) + super(EmailOption, self)._validate(domain) + + +class URLOption(DomainnameOption): + __slots__ = tuple() + _opt_type = 'url' + proto_re = re.compile(r'(http|https)://') + path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") + + def _validate(self, value): + match = self.proto_re.search(value) + if not match: + raise ValueError(_('invalid url, must start with http:// or ' + 'https://')) + value = value[len(match.group(0)):] + # get domain/files + splitted = value.split('/', 1) + try: + domain, files = splitted + except ValueError: + domain = value + files = None + # if port in domain + splitted = domain.split(':', 1) + try: + domain, port = splitted + + except ValueError: + domain = splitted[0] + port = 0 + if not 0 <= int(port) <= 65535: + raise ValueError(_('invalid url, port must be an between 0 and ' + '65536')) + # validate domainname + super(URLOption, self)._validate(domain) + # validate file + if files is not None and files != '' and not self.path_re.search(files): + raise ValueError(_('invalid url, must ends with filename')) + + +class UsernameOption(Option): + __slots__ = tuple() + _opt_type = 'username' + #regexp build with 'man 8 adduser' informations + username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") + + def _validate(self, value): + match = self.username_re.search(value) + if not match: + raise ValueError(_('invalid username')) + + +class FilenameOption(Option): + __slots__ = tuple() + _opt_type = 'file' + path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") + + def _validate(self, value): + match = self.path_re.search(value) + if not match: + raise ValueError(_('invalid filename')) + + class OptionDescription(BaseOption): """Config's schema (organisation, group) and container of Options The `OptionsDescription` objects lives in the `tiramisu.config.Config`. @@ -1125,11 +1255,12 @@ class OptionDescription(BaseOption): if not force_no_consistencies and \ option._consistencies is not None: for consistency in option._consistencies: - func, all_cons_opts = consistency + func, all_cons_opts, params = consistency for opt in all_cons_opts: _consistencies.setdefault(opt, []).append((func, - all_cons_opts)) + all_cons_opts, + params)) else: _currpath.append(attr) option.impl_build_cache(cache_path, @@ -1176,7 +1307,6 @@ class OptionDescription(BaseOption): self._group_type = group_type if isinstance(group_type, groups.MasterGroupType): #if master (same name has group) is set - identical_master_child_name = False #for collect all slaves slaves = [] master = None @@ -1193,7 +1323,6 @@ class OptionDescription(BaseOption): ": this option is not a multi" "").format(child._name, self._name)) if child._name == self._name: - identical_master_child_name = True child._multitype = multitypes.master master = child else: @@ -1202,14 +1331,18 @@ class OptionDescription(BaseOption): raise ValueError(_('master group with wrong' ' master name for {0}' ).format(self._name)) + if master._callback is not None and master._callback[1] is not None: + for key, callbacks in master._callback[1].items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + if callbk[0] in slaves: + raise ValueError(_("callback of master's option shall " + "not refered a slave's ones")) master._master_slaves = tuple(slaves) for child in self.impl_getchildren(): if child != master: child._master_slaves = master child._multitype = multitypes.slave - if not identical_master_child_name: - raise ValueError(_("no child has same nom has master group" - " for: {0}").format(self._name)) else: raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) @@ -1223,15 +1356,20 @@ class OptionDescription(BaseOption): #consistencies is something like [('_cons_not_equal', (opt1, opt2))] consistencies = self._cache_consistencies.get(option) if consistencies is not None: - for func, all_cons_opts in consistencies: + for func, all_cons_opts, params in consistencies: + warnings_only = params.get('warnings_only', False) #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 + try: + all_cons_opts[0]._launch_consistency(func, option, + value, + context, index, + all_cons_opts, + warnings_only) + except ValueError as err: + if warnings_only: + raise ValueWarning(err.message, option) + else: + raise err def _impl_getstate(self, descr=None): """enables us to export into a dict @@ -1341,7 +1479,7 @@ def validate_requires_arg(requires, name): 'must be an option in option {0}').format(name)) if option.impl_is_multi(): raise ValueError(_('malformed requirements option {0} ' - 'should not be a multi').format(name)) + 'must not be a multi').format(name)) if expected is not None: try: option._validate(expected) @@ -1376,17 +1514,17 @@ def validate_requires_arg(requires, name): def validate_callback(callback, callback_params, type_): if type(callback) != FunctionType: - raise ValueError(_('{0} should be a function').format(type_)) + raise ValueError(_('{0} must be a function').format(type_)) if callback_params is not None: if not isinstance(callback_params, dict): - raise ValueError(_('{0}_params should be a dict').format(type_)) + raise ValueError(_('{0}_params must be a dict').format(type_)) for key, callbacks in callback_params.items(): if key != '' and len(callbacks) != 1: - raise ValueError(_('{0}_params with key {1} should not have ' - 'length different to 1').format(type_, + raise ValueError(_("{0}_params with key {1} mustn't have " + "length different to 1").format(type_, key)) if not isinstance(callbacks, tuple): - raise ValueError(_('{0}_params should be tuple for key "{1}"' + raise ValueError(_('{0}_params must be tuple for key "{1}"' ).format(type_, key)) for callbk in callbacks: if isinstance(callbk, tuple): @@ -1395,11 +1533,11 @@ def validate_callback(callback, callback_params, type_): raise ValueError(_('validator not support tuple')) if not isinstance(option, Option) and not \ isinstance(option, SymLinkOption): - raise ValueError(_('{0}_params should have an option ' + raise ValueError(_('{0}_params must have an option ' 'not a {0} for first argument' ).format(type_, type(option))) if force_permissive not in [True, False]: - raise ValueError(_('{0}_params should have a boolean' + raise ValueError(_('{0}_params must have a boolean' ' not a {0} for second argument' ).format(type_, type( force_permissive))) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index bea7b7a..6ba84d7 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -19,7 +19,7 @@ from time import time from copy import copy import weakref from tiramisu.error import (RequirementError, PropertiesOptionError, - ConstError) + ConstError, ConfigError) from tiramisu.i18n import _ @@ -237,6 +237,14 @@ multitypes = MultiTypeModule() populate_multitypes() +# ____________________________________________________________ +class Undefined(): + pass + + +undefined = Undefined() + + # ____________________________________________________________ class Property(object): "a property is responsible of the option's value access rules" @@ -249,6 +257,11 @@ class Property(object): self._properties = prop def append(self, propname): + """Appends a property named propname + + :param propname: a predefined or user defined property name + :type propname: string + """ if self._opt is not None and self._opt._calc_properties is not None \ and propname in self._opt._calc_properties: raise ValueError(_('cannot append {0} property for option {1}: ' @@ -258,12 +271,29 @@ class Property(object): self._setting._setproperties(self._properties, self._opt, self._path) def remove(self, propname): + """Removes a property named propname + + :param propname: a predefined or user defined property name + :type propname: string + """ if propname in self._properties: self._properties.remove(propname) self._setting._setproperties(self._properties, self._opt, self._path) + def extend(self, propnames): + """Extends properties to the existing properties + + :param propnames: an iterable made of property names + :type propnames: iterable of string + """ + for propname in propnames: + self.append(propname) + def reset(self): + """resets the properties (does not **clear** the properties, + default properties are still present) + """ self._setting.reset(_path=self._path) def __contains__(self, propname): @@ -275,7 +305,7 @@ class Property(object): #____________________________________________________________ class Settings(object): - "``Config()``'s configuration options" + "``config.Config()``'s configuration options settings" __slots__ = ('context', '_owner', '_p_', '__weakref__') def __init__(self, context, storage): @@ -293,6 +323,17 @@ class Settings(object): self.context = weakref.ref(context) self._p_ = storage + def _getcontext(self): + """context could be None, we need to test it + context is None only if all reference to `Config` object is deleted + (for example we delete a `Config` and we manipulate a reference to + old `SubConfig`, `Values`, `Multi` or `Settings`) + """ + context = self.context() + if context is None: + raise ConfigError(_('the context does not exist anymore')) + return context + #____________________________________________________________ # properties methods def __contains__(self, propname): @@ -317,16 +358,16 @@ class Settings(object): raise ValueError(_('opt and all_properties must not be set ' 'together in reset')) if all_properties: - self._p_.reset_all_propertives() + self._p_.reset_all_properties() else: if opt is not None and _path is None: _path = self._get_path_by_opt(opt) self._p_.reset_properties(_path) - self.context().cfgimpl_reset_cache() + self._getcontext().cfgimpl_reset_cache() 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' @@ -337,8 +378,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: @@ -346,7 +387,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): @@ -362,6 +403,10 @@ class Settings(object): props.remove(propname) self._setproperties(props, None, None) + def extend(self, propnames): + for propname in propnames: + self.append(propname) + def _setproperties(self, properties, opt, path): """save properties for specified opt (never save properties if same has option properties) @@ -375,7 +420,7 @@ class Settings(object): self._p_.reset_properties(path) else: self._p_.setproperties(path, properties) - self.context().cfgimpl_reset_cache() + self._getcontext().cfgimpl_reset_cache() #____________________________________________________________ def validate_properties(self, opt_or_descr, is_descr, is_write, path, @@ -400,11 +445,13 @@ class Settings(object): (typically with the `frozen` property) """ # opt properties - properties = copy(self._getproperties(opt_or_descr, path)) + 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 properties -= self._p_.getpermissive(path) # remove global permissive if need - self_properties = copy(self._getproperties()) if force_permissive is True or 'permissive' in self_properties: properties -= self._p_.getpermissive() if force_permissives is not None: @@ -421,7 +468,7 @@ class Settings(object): properties -= frozenset(('mandatory', 'frozen')) else: if 'mandatory' in properties and \ - not self.context().cfgimpl_get_values()._isempty( + not self._getcontext().cfgimpl_get_values()._isempty( opt_or_descr, value): properties.remove('mandatory') if is_write and 'everything_frozen' in self_properties: @@ -544,6 +591,7 @@ class Settings(object): # filters the callbacks calc_properties = set() + context = self._getcontext() for requires in opt._requires: for require in requires: option, expected, action, inverse, \ @@ -555,8 +603,7 @@ class Settings(object): " '{0}' with requirement on: " "'{1}'").format(path, reqpath)) try: - value = self.context()._getattr(reqpath, - force_permissive=True) + value = context._getattr(reqpath, force_permissive=True) except PropertiesOptionError as err: if not transitive: continue @@ -585,7 +632,7 @@ class Settings(object): :param opt: `Option`'s object :returns: path """ - return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt) + return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt) def get_modified_properties(self): return self._p_.get_modified_properties() diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 73758f4..a25c989 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -29,7 +29,7 @@ class Settings(Cache): self._permissives = {} super(Settings, self).__init__(storage) - # propertives + # properties def setproperties(self, path, properties): self._properties[path] = properties @@ -39,7 +39,7 @@ class Settings(Cache): def hasproperties(self, path): return path in self._properties - def reset_all_propertives(self): + def reset_all_properties(self): self._properties.clear() def reset_properties(self, path): diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index 0b71dd2..a4478d2 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -31,7 +31,7 @@ class Settings(Sqlite3DB): self._storage.execute(settings_table, commit=False) self._storage.execute(permissives_table) - # propertives + # properties def setproperties(self, path, properties): path = self._sqlite_encode_path(path) self._storage.execute("DELETE FROM property WHERE path = ?", (path,), @@ -54,7 +54,7 @@ class Settings(Sqlite3DB): return self._storage.select("SELECT properties FROM property WHERE " "path = ?", (path,)) is not None - def reset_all_propertives(self): + def reset_all_properties(self): self._storage.execute("DELETE FROM property") def reset_properties(self, path): diff --git a/tiramisu/value.py b/tiramisu/value.py index 7fd34db..968f0e4 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -19,8 +19,8 @@ from time import time from copy import copy import sys import weakref -from tiramisu.error import ConfigError, SlaveError -from tiramisu.setting import owners, multitypes, expires_time +from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError +from tiramisu.setting import owners, multitypes, expires_time, undefined from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ from tiramisu.option import SymLinkOption @@ -44,15 +44,28 @@ class Values(object): # the storage type is dictionary or sqlite3 self._p_ = storage + def _getcontext(self): + """context could be None, we need to test it + context is None only if all reference to `Config` object is deleted + (for example we delete a `Config` and we manipulate a reference to + old `SubConfig`, `Values`, `Multi` or `Settings`) + """ + context = self.context() + if context is None: + raise ConfigError(_('the context does not exist anymore')) + return context + def _getdefault(self, opt): """ actually retrieves the default value :param opt: the `option.Option()` object """ - meta = self.context().cfgimpl_get_meta() + 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(): @@ -60,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 @@ -69,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): @@ -103,11 +111,11 @@ class Values(object): if path is None: path = self._get_opt_path(opt) if self._p_.hasvalue(path): - setting = self.context().cfgimpl_get_settings() + context = self._getcontext() + setting = context.cfgimpl_get_settings() opt.impl_validate(opt.impl_getdefault(), - self.context(), - 'validator' in setting) - self.context().cfgimpl_reset_cache() + context, 'validator' in setting) + context.cfgimpl_reset_cache() if (opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master): for slave in opt.impl_get_master_slaves(): @@ -135,7 +143,7 @@ class Values(object): callback, callback_params = opt._callback if callback_params is None: callback_params = {} - return carry_out_calculation(opt._name, config=self.context(), + return carry_out_calculation(opt, config=self._getcontext(), callback=callback, callback_params=callback_params, index=index, max_len=max_len) @@ -149,7 +157,7 @@ class Values(object): if path is None: path = self._get_opt_path(opt) ntime = None - setting = self.context().cfgimpl_get_settings() + setting = self._getcontext().cfgimpl_get_settings() if 'cache' in setting and self._p_.hascache(path): if 'expire' in setting: ntime = int(time()) @@ -174,7 +182,8 @@ class Values(object): def _getitem(self, opt, path, validate, force_permissive, force_properties, validate_properties): # options with callbacks - setting = self.context().cfgimpl_get_settings() + context = self._getcontext() + setting = context.cfgimpl_get_settings() is_frozen = 'frozen' in setting[opt] # For calculating properties, we need value (ie for mandatory value). # If value is calculating with a PropertiesOptionError's option @@ -184,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 @@ -194,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(self.context(), masterp) + mastervalue = context._getattr(masterp, validate=validate) lenmaster = len(mastervalue) if lenmaster == 0: value = [] @@ -226,9 +235,12 @@ 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, self.context(), 'validator' in setting) + opt.impl_validate(value, context, 'validator' in setting) if config_error is None and self._is_default_owner(path) and \ 'force_store_value' in setting[opt]: self.setitem(opt, value, path, is_write=False) @@ -249,24 +261,45 @@ class Values(object): # is_write is, for example, used with "force_store_value" # user didn't change value, so not write # valid opt - opt.impl_validate(value, self.context(), - 'validator' in self.context().cfgimpl_get_settings()) - if opt.impl_is_multi() and not isinstance(value, Multi): + context = self._getcontext() + opt.impl_validate(value, context, + 'validator' in context.cfgimpl_get_settings()) + 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, is_write=True, validate_properties=True): - self.context().cfgimpl_reset_cache() + context = self._getcontext() + context.cfgimpl_reset_cache() if validate_properties: - setting = self.context().cfgimpl_get_settings() + setting = context.cfgimpl_get_settings() setting.validate_properties(opt, False, is_write, value=value, path=path, force_permissive=force_permissive, force_properties=force_properties) - owner = self.context().cfgimpl_get_settings().getowner() + owner = context.cfgimpl_get_settings().getowner() + if isinstance(value, Multi): + value = list(value) self._p_.setvalue(path, value, owner) def getowner(self, opt): @@ -283,7 +316,7 @@ class Values(object): def _getowner(self, path): owner = self._p_.getowner(path, owners.default) - meta = self.context().cfgimpl_get_meta() + meta = self._getcontext().cfgimpl_get_meta() if owner is owners.default and meta is not None: owner = meta.cfgimpl_get_values()._getowner(path) return owner @@ -335,7 +368,7 @@ class Values(object): :param opt: the `option.Option` object :returns: a string with points like "gc.dummy.my_option" """ - return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt) + return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt) # information def set_information(self, key, value): @@ -360,6 +393,42 @@ class Values(object): raise ValueError(_("information's item" " not found: {0}").format(key)) + def mandatory_warnings(self): + """convenience function to trace Options that are mandatory and + where no value has been set + + :returns: generator of mandatory Option's path + + """ + #if value in cache, properties are not calculated + self.reset_cache(False) + context = self.context() + for path in context.cfgimpl_get_description().impl_getpaths( + include_groups=True): + try: + context._getattr(path, + force_properties=frozenset(('mandatory',))) + except PropertiesOptionError as err: + if err.proptype == ['mandatory']: + yield path + self.reset_cache(False) + + def force_cache(self): + """parse all option to force data in cache + """ + context = self.context() + if not 'cache' in context.cfgimpl_get_settings(): + raise ConfigError(_('can force cache only if cache ' + 'is actived in config')) + #remove all cached properties and value to update "expired" time + context.cfgimpl_reset_cache() + for path in context.cfgimpl_get_description().impl_getpaths( + include_groups=True): + try: + context._getattr(path) + except PropertiesOptionError: + pass + def __getstate__(self): return {'_p_': self._p_} @@ -387,6 +456,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): @@ -396,21 +467,32 @@ 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): + """context could be None, we need to test it + context is None only if all reference to `Config` object is deleted + (for example we delete a `Config` and we manipulate a reference to + old `SubConfig`, `Values`, `Multi` or `Settings`) + """ + context = self.context() + if context is None: + raise ConfigError(_('the context does not exist anymore')) + return context + def _valid_slave(self, value, setitem): #if slave, had values until master's one - values = self.context().cfgimpl_get_values() - masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt( + context = self._getcontext() + values = context.cfgimpl_get_values() + masterp = context.cfgimpl_get_description().impl_get_path_by_opt( self.opt.impl_get_master_slaves()) - mastervalue = getattr(self.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)) @@ -427,59 +509,43 @@ class Multi(list): #else: same len so do nothing return value - def _valid_master(self, value): - masterlen = len(value) - values = self.context().cfgimpl_get_values() + 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(slave.impl_getdefault_multi(), - force=True) + Multi(values._getvalue(slave, path), self.context, slave, path) def __setitem__(self, index, value): self._validate(value, index) #assume not checking mandatory property super(Multi, self).__setitem__(index, value) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) - def append(self, value, force=False): + def append(self, value=undefined, force=False): """the list value can be updated (appened) only if the option is a master """ + context = self._getcontext() if not force: if self.opt.impl_get_multitype() == multitypes.slave: raise SlaveError(_("cannot append a value on a multi option {0}" " which is a slave").format(self.opt._name)) elif self.opt.impl_get_multitype() == multitypes.master: - values = self.context().cfgimpl_get_values() - if value is None and self.opt.impl_has_callback(): + values = context.cfgimpl_get_values() + if value is undefined and self.opt.impl_has_callback(): value = values._getcallback_value(self.opt) #Force None il return a list if isinstance(value, list): value = None index = self.__len__() + if value is undefined: + value = self.opt.impl_getdefault_multi() self._validate(value, index) super(Multi, self).append(value) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, - self, - validate_properties=not force) + context.cfgimpl_get_values()._setvalue(self.opt, self.path, + self, + validate_properties=not force) if not force and self.opt.impl_get_multitype() == multitypes.master: for slave in self.opt.impl_get_master_slaves(): path = values._get_opt_path(slave) @@ -488,16 +554,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, @@ -510,7 +575,7 @@ class Multi(list): super(Multi, self).sort(key=key, reverse=reverse) else: super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) def reverse(self): if self.opt.impl_get_multitype() in [multitypes.slave, @@ -518,7 +583,7 @@ class Multi(list): raise SlaveError(_("cannot reverse multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).reverse() - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) def insert(self, index, obj): if self.opt.impl_get_multitype() in [multitypes.slave, @@ -526,7 +591,7 @@ class Multi(list): raise SlaveError(_("cannot insert multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).insert(index, obj) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) def extend(self, iterable): if self.opt.impl_get_multitype() in [multitypes.slave, @@ -534,12 +599,12 @@ class Multi(list): raise SlaveError(_("cannot extend multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).extend(iterable) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) def _validate(self, value, force_index): if value is not None: try: - self.opt.impl_validate(value, context=self.context(), + self.opt.impl_validate(value, context=self._getcontext(), force_index=force_index) except ValueError as err: raise ValueError(_("invalid value {0} " @@ -557,19 +622,20 @@ class Multi(list): :type force: boolean :returns: item at index """ + context = self._getcontext() if not force: 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 = self.context().cfgimpl_get_values() + 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 ret = super(Multi, self).pop(index) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force) + context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force) return ret diff --git a/translations/fr/tiramisu.po b/translations/fr/tiramisu.po index 256a0da..3dee004 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -1,63 +1,61 @@ msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: Tiramisu\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-09-28 19:06+CEST\n" +"POT-Creation-Date: 2014-03-12 21:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Emmanuel Garette \n" -"Language-Team: LANGUAGE \n" +"Language-Team: Tiramisu's team \n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" -#: tiramisu/autolib.py:144 +#: tiramisu/autolib.py:162 msgid "" "unable to carry out a calculation, option {0} has properties: {1} for: {2}" msgstr "" "impossible d'effectuer le calcul, l'option {0} a les propriétés : {1} pour : " "{2}" -#: tiramisu/autolib.py:153 -msgid "" -"unable to carry out a calculation, option value with multi types must have " -"same length for: {0}" -msgstr "" -"impossible d'effectuer le calcul, la valeur d'une option avec le type multi " -"doit avoir la même longueur pour : {0}" - -#: tiramisu/config.py:51 +#: tiramisu/config.py:52 msgid "descr must be an optiondescription, not {0}" msgstr "descr doit être une optiondescription pas un {0}" -#: tiramisu/config.py:126 +#: tiramisu/config.py:127 msgid "unknown group_type: {0}" msgstr "group_type inconnu: {0}" -#: tiramisu/config.py:162 -msgid "" -"no option description found for this config (may be metaconfig without meta)" -msgstr "" -"pas d'option description trouvé pour cette config (peut être une metaconfig " -"sans meta)" +#: tiramisu/config.py:164 tiramisu/setting.py:339 tiramisu/value.py:57 +#: tiramisu/value.py:485 +msgid "the context does not exist anymore" +msgstr "le context n'existe plus" -#: tiramisu/config.py:188 +#: tiramisu/config.py:169 +msgid "no option description found for this config (may be GroupConfig)" +msgstr "" +"pas d'option description trouvé pour cette config (peut être un GroupConfig)" + +#: tiramisu/config.py:195 msgid "can't assign to an OptionDescription" msgstr "ne peut pas attribuer une valeur à une OptionDescription" -#: tiramisu/config.py:319 +#: tiramisu/config.py:325 msgid "unknown type_ type {0}for _find" msgstr "type_ type {0} pour _find inconnu" -#: tiramisu/config.py:358 +#: tiramisu/config.py:364 msgid "no option found in config with these criteria" msgstr "aucune option trouvée dans la config avec ces critères" -#: tiramisu/config.py:408 +#: tiramisu/config.py:414 msgid "make_dict can't filtering with value without option" msgstr "make_dict ne peut filtrer sur une valeur mais sans option" -#: tiramisu/config.py:429 +#: tiramisu/config.py:435 msgid "unexpected path {0}, should start with {1}" msgstr "chemin imprévu {0}, devrait commencer par {1}" @@ -65,41 +63,66 @@ msgstr "chemin imprévu {0}, devrait commencer par {1}" msgid "opt in getowner must be an option not {0}" msgstr "opt dans getowner doit être une option pas {0}" -#: tiramisu/option.py:68 +#: tiramisu/config.py:532 +msgid "cannot serialize Config with MetaConfig" +msgstr "impossible de sérialiser une Config avec une MetaConfig" + +#: tiramisu/config.py:546 +msgid "this storage is not serialisable, could be a none persistent storage" +msgstr "ce storage n'est sérialisable, devrait être une storage non persistant" + +#: tiramisu/config.py:609 +msgid "metaconfig's children must be a list" +msgstr "enfants d'une metaconfig doit être une liste" + +#: tiramisu/config.py:703 +msgid "metaconfig's children should be config, not {0}" +msgstr "enfants d'une metaconfig doit être une config, pas {0}" + +#: tiramisu/config.py:707 +msgid "child has already a metaconfig's" +msgstr "enfant a déjà une metaconfig" + +#: tiramisu/config.py:711 +msgid "all config in metaconfig must have the same optiondescription" +msgstr "" +"toutes les configs d'une metaconfig doivent avoir la même optiondescription" + +#: tiramisu/option.py:67 msgid "invalid name: {0} for option" msgstr "nom invalide : {0} pour l'option" -#: tiramisu/option.py:77 +#: tiramisu/option.py:76 msgid "invalid properties type {0} for {1}, must be a tuple" msgstr "type des properties invalide {0} pour {1}, doit être un tuple" -#: tiramisu/option.py:115 +#: tiramisu/option.py:114 msgid "'{0}' ({1}) object attribute '{2}' is read-only" msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seule" -#: tiramisu/option.py:142 tiramisu/value.py:360 +#: tiramisu/option.py:141 tiramisu/value.py:395 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 +#: tiramisu/option.py:203 msgid "cannot serialize Option, only in OptionDescription" msgstr "ne peut serialiser une Option, seulement via une OptionDescription" -#: tiramisu/option.py:307 +#: tiramisu/option.py:306 msgid "a default_multi is set whereas multi is False in option: {0}" msgstr "" "une default_multi est renseignée alors que multi est False dans l'option : " "{0}" -#: tiramisu/option.py:313 +#: tiramisu/option.py:312 msgid "invalid default_multi value {0} for option {1}: {2}" msgstr "la valeur default_multi est invalide {0} pour l'option {1} : {2}" -#: tiramisu/option.py:318 +#: tiramisu/option.py:317 msgid "default value not allowed if option: {0} is calculated" msgstr "la valeur par défaut n'est pas possible si l'option {0} est calculée" -#: tiramisu/option.py:321 +#: tiramisu/option.py:320 msgid "" "params defined for a callback function but no callback defined yet for " "option {0}" @@ -107,221 +130,289 @@ msgstr "" "params définis pour une fonction callback mais par de callback encore " "définis pour l'option {0}" -#: tiramisu/option.py:360 -msgid "option not in all_cons_opts" -msgstr "option non présentante dans all_cons_opts" +#: tiramisu/option.py:425 tiramisu/option.py:450 +msgid "invalid value for option {0}: {1}" +msgstr "valeur invalide pour l'option {0} : {1}" -#: tiramisu/option.py:432 tiramisu/value.py:545 -msgid "invalid value {0} for option {1}: {2}" -msgstr "valeur invalide {0} pour l'option {1} : {2}" +#: tiramisu/option.py:444 +msgid "warning on the value of the option {0}: {1}" +msgstr "avertissement sur la valeur de l'option {0} : {1}" -#: tiramisu/option.py:449 -msgid "which must be a list" -msgstr "lequel doit être une liste" +#: tiramisu/option.py:461 +msgid "invalid value {0} for option {1} which must be a list" +msgstr "valeur invalide pour l'option {0} : {1} laquelle doit être une liste" -#: tiramisu/option.py:509 -msgid "consistency should be set with an option" +#: tiramisu/option.py:519 +msgid "consistency must be set with an option" msgstr "consistency doit être configuré avec une option" -#: tiramisu/option.py:511 +#: tiramisu/option.py:521 msgid "cannot add consistency with itself" msgstr "ne peut ajouter une consistency avec lui même" -#: tiramisu/option.py:513 -msgid "every options in consistency should be multi or none" +#: tiramisu/option.py:523 +msgid "every options in consistency must be multi or none" msgstr "" -"toutes les options d'une consistency devrait être multi ou ne pas l'être" +"toutes les options d'une consistency doivent être multi ou ne pas l'être" -#: tiramisu/option.py:533 -msgid "same value for {0} and {1}" -msgstr "même valeur pour {0} et {1}" +#: tiramisu/option.py:544 +msgid "same value for {0} and {1}, should be different" +msgstr "même valeur pour {0} et {1}, devrait être différent" -#: tiramisu/option.py:642 +#: tiramisu/option.py:546 +msgid "same value for {0} and {1}, must be different" +msgstr "même valeur pour {0} et {1}, doit être différent" + +#: tiramisu/option.py:640 msgid "values must be a tuple for {0}" msgstr "values doit être un tuple pour {0}" -#: tiramisu/option.py:645 +#: tiramisu/option.py:643 msgid "open_values must be a boolean for {0}" msgstr "open_values doit être un booléen pour {0}" -#: tiramisu/option.py:667 +#: tiramisu/option.py:665 msgid "value {0} is not permitted, only {1} is allowed" msgstr "valeur {0} n'est pas permis, seules {1} sont autorisées" -#: tiramisu/option.py:679 -msgid "value must be a boolean" -msgstr "valeur doit être un booléen" +#: tiramisu/option.py:677 +msgid "invalid boolean" +msgstr "booléen invalide" -#: tiramisu/option.py:689 -msgid "value must be an integer" -msgstr "valeur doit être un nombre entier" +#: tiramisu/option.py:687 +msgid "invalid integer" +msgstr "nombre invalide" -#: tiramisu/option.py:699 -msgid "value must be a float" -msgstr "valeur doit être un nombre flottant" +#: tiramisu/option.py:697 +msgid "invalid float" +msgstr "invalide nombre flottan" -#: tiramisu/option.py:709 -msgid "value must be a string, not {0}" -msgstr "valeur doit être une chaîne, pas {0}" +#: tiramisu/option.py:707 +msgid "invalid string" +msgstr "invalide caractère" -#: tiramisu/option.py:727 -msgid "value must be an unicode" -msgstr "valeur doit être une valeur unicode" +#: tiramisu/option.py:724 +msgid "invalid unicode" +msgstr "invalide unicode" -#: tiramisu/option.py:739 +#: tiramisu/option.py:736 msgid "malformed symlinkoption must be an option for symlink {0}" msgstr "symlinkoption mal formé, doit être une option pour symlink {0}" -#: tiramisu/option.py:788 -msgid "invalid IP {0}" -msgstr "adresse IP invalide {0}" +#: tiramisu/option.py:787 tiramisu/option.py:790 tiramisu/option.py:795 +msgid "invalid IP" +msgstr "adresse IP invalide" -#: tiramisu/option.py:793 -msgid "IP mustn't not be in reserved class" -msgstr "IP ne doit pas être d'une classe reservée" +#: tiramisu/option.py:801 +msgid "IP shouldn't be in reserved class" +msgstr "l'adresse IP ne devrait pas être d'une classe réservée" -#: tiramisu/option.py:795 -msgid "IP must be in private class" -msgstr "IP doit être dans la classe privée" +#: tiramisu/option.py:803 +msgid "invalid IP, mustn't be in reserved class" +msgstr "adresse IP invalide, ne doit pas être dans une classe réservée" -#: tiramisu/option.py:833 -msgid "inconsistency in allowed range" -msgstr "inconsistence dans la plage autorisée" +#: tiramisu/option.py:807 +msgid "IP should be in private class" +msgstr "l'adresse IP devrait être dans une classe privée" -#: tiramisu/option.py:838 -msgid "max value is empty" -msgstr "la valeur maximum est vide" +#: tiramisu/option.py:809 +msgid "invalid IP, must be in private class" +msgstr "adresse IP invalide, doit être dans la classe privée" -#: tiramisu/option.py:877 -msgid "invalid network address {0}" -msgstr "adresse réseau invalide {0}" - -#: tiramisu/option.py:882 -msgid "network shall not be in reserved class" -msgstr "le réseau ne doit pas être dans la classe reservée" - -#: tiramisu/option.py:894 -msgid "invalid netmask address {0}" -msgstr "masque de sous-réseau invalide {0}" - -#: tiramisu/option.py:910 -msgid "invalid len for opts" -msgstr "longueur invalide pour opts" - -#: tiramisu/option.py:922 -msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" -msgstr "réseau invalide {0} ({1}) avec masque {2} ({3}), ce réseau est une IP" - -#: tiramisu/option.py:927 -msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" -msgstr "IP invalide {0} ({1}) avec masque {2} ({3}), cette IP est un réseau" - -#: tiramisu/option.py:932 -msgid "invalid IP {0} ({1}) with netmask {2} ({3})" -msgstr "IP invalide {0} ({1}) avec masque {2} ({3})" - -#: tiramisu/option.py:934 -msgid "invalid network {0} ({1}) with netmask {2} ({3})" -msgstr "réseau invalide {0} ({1}) avec masque {2} ({3})" - -#: tiramisu/option.py:948 -msgid "invalid broadcast address {0}" -msgstr "adresse de broadcast invalide {0}" - -#: tiramisu/option.py:952 +#: tiramisu/option.py:814 tiramisu/option.py:989 msgid "invalid len for vals" msgstr "longueur invalide pour vals" -#: tiramisu/option.py:957 +#: tiramisu/option.py:820 +msgid "IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})" +msgstr "IP {0} ({1}) pas dans le réseau {2} ({3}) avec le masque {4} ({5})" + +#: tiramisu/option.py:823 +msgid "invalid IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})" +msgstr "" +"IP invalide {0} ({1}) pas dans le réseau {2} ({3}) avec le masque {4} ({5})" + +#: tiramisu/option.py:864 +msgid "inconsistency in allowed range" +msgstr "inconsistence dans la plage autorisée" + +#: tiramisu/option.py:869 +msgid "max value is empty" +msgstr "la valeur maximum est vide" + +#: tiramisu/option.py:886 +msgid "invalid port, range must have two values only" +msgstr "port invalide, une plage doit avoir deux valeurs seulement" + +#: tiramisu/option.py:889 +msgid "invalid port, first port in range must be smaller than the second one" +msgstr "" +"port invalide, le premier port d'une plage doit être plus petit que le second" + +#: tiramisu/option.py:898 +msgid "invalid port" +msgstr "port invalide" + +#: tiramisu/option.py:900 +msgid "invalid port, must be an between {0} and {1}" +msgstr "port invalide, port doit être entre {0} et {1}" + +#: tiramisu/option.py:914 +msgid "invalid network address" +msgstr "adresse réseau invalide" + +#: tiramisu/option.py:920 +msgid "network address shouldn't be in reserved class" +msgstr "l'adresse réseau ne devait pas être dans la classe réservée" + +#: tiramisu/option.py:922 +msgid "invalid network address, mustn't be in reserved class" +msgstr "adresse réseau invalide, ne doit pas être dans la classe réservée" + +#: tiramisu/option.py:935 +msgid "invalid netmask address" +msgstr "masque de sous-réseau invalide" + +#: tiramisu/option.py:952 +msgid "invalid len for opts" +msgstr "longueur invalide pour opts" + +#: tiramisu/option.py:966 +msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network" +msgstr "IP invalide {0} ({1}) avec masque {2}, cette IP est un réseau" + +#: tiramisu/option.py:971 +msgid "invalid network {0} ({1}) with netmask {2}" +msgstr "réseau invalide {0} ({1}) avec masque {2}" + +#: tiramisu/option.py:985 +msgid "invalid broadcast address" +msgstr "adresse de broadcast invalide" + +#: tiramisu/option.py:994 msgid "" "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})" msgstr "" "Broadcast invalide {0} ({1}) avec le réseau {2} ({3}) et le masque {4} ({5})" -#: tiramisu/option.py:979 +#: tiramisu/option.py:1016 msgid "unknown type_ {0} for hostname" msgstr "type_ inconnu {0} pour le nom d'hôte" -#: tiramisu/option.py:982 +#: tiramisu/option.py:1019 msgid "allow_ip must be a boolean" msgstr "allow_ip doit être un booléen" -#: tiramisu/option.py:1012 -msgid "invalid value for {0}, must have dot" -msgstr "valeur invalide pour {0}, doit avoir un point" +#: tiramisu/option.py:1021 +msgid "allow_without_dot must be a boolean" +msgstr "allow_without_dot doit être un booléen" -#: tiramisu/option.py:1015 -msgid "invalid domainname's length for {0} (max {1})" -msgstr "longueur du nom de domaine invalide pour {0} (maximum {1})" +#: tiramisu/option.py:1065 +msgid "invalid domainname, must have dot" +msgstr "nom de domaine invalide, doit avoir un point" -#: tiramisu/option.py:1018 -msgid "invalid domainname's length for {0} (min 2)" -msgstr "longueur du nom de domaine invalide pour {0} (minimum 2)" +#: tiramisu/option.py:1067 +msgid "invalid domainname's length (max 255)" +msgstr "longueur du nom de domaine invalide (maximum {1})" -#: tiramisu/option.py:1022 +#: tiramisu/option.py:1069 +msgid "invalid domainname's length (min 2)" +msgstr "longueur du nom de domaine invalide (minimum 2)" + +#: tiramisu/option.py:1071 msgid "invalid domainname" msgstr "nom de domaine invalide" -#: tiramisu/option.py:1049 +#: tiramisu/option.py:1084 +msgid "invalid email address, must contains one @" +msgstr "adresse email invalide, doit contenir un @" + +#: tiramisu/option.py:1087 +msgid "invalid username in email address" +msgstr "nom d'utilisateur invalide dans une adresse email" + +#: tiramisu/option.py:1100 +msgid "invalid url, must start with http:// or https://" +msgstr "URL invalide, doit démarrer avec http:// ou https://" + +#: tiramisu/option.py:1119 +msgid "invalid url, port must be an between 0 and 65536" +msgstr "URL invalide, port doit être entre 0 et 65536" + +#: tiramisu/option.py:1125 +msgid "invalid url, must ends with filename" +msgstr "URL invalide, doit finir avec un nom de fichier" + +#: tiramisu/option.py:1137 +msgid "invalid username" +msgstr "utilisateur invalide" + +#: tiramisu/option.py:1148 +msgid "invalid filename" +msgstr "nom de fichier invalide" + +#: tiramisu/option.py:1175 msgid "duplicate option name: {0}" msgstr "nom de l'option dupliqué : {0}" -#: tiramisu/option.py:1067 +#: tiramisu/option.py:1193 msgid "unknown Option {0} in OptionDescription {1}" msgstr "Option {0} inconnue pour l'OptionDescription {1}" -#: tiramisu/option.py:1118 +#: tiramisu/option.py:1244 msgid "duplicate option: {0}" msgstr "option dupliquée : {0}" -#: tiramisu/option.py:1148 +#: tiramisu/option.py:1275 msgid "consistency with option {0} which is not in Config" msgstr "consistency avec l'option {0} qui n'est pas dans une Config" -#: tiramisu/option.py:1156 +#: tiramisu/option.py:1283 msgid "no option for path {0}" msgstr "pas d'option pour le chemin {0}" -#: tiramisu/option.py:1162 +#: tiramisu/option.py:1289 msgid "no option {0} found" msgstr "pas d'option {0} trouvée" -#: tiramisu/option.py:1172 +#: tiramisu/option.py:1299 msgid "cannot change group_type if already set (old {0}, new {1})" msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1})" -#: tiramisu/option.py:1185 +#: tiramisu/option.py:1311 msgid "master group {0} shall not have a subgroup" msgstr "groupe maître {0} ne doit pas avoir de sous-groupe" -#: tiramisu/option.py:1188 +#: tiramisu/option.py:1314 msgid "master group {0} shall not have a symlinkoption" msgstr "groupe maître {0} ne doit pas avoir de symlinkoption" -#: tiramisu/option.py:1191 +#: tiramisu/option.py:1317 msgid "not allowed option {0} in group {1}: this option is not a multi" msgstr "" "option non autorisée {0} dans le groupe {1} : cette option n'est pas une " "multi" -#: tiramisu/option.py:1202 +#: tiramisu/option.py:1327 msgid "master group with wrong master name for {0}" msgstr "le groupe maître avec un nom de maître érroné pour {0}" -#: tiramisu/option.py:1211 -msgid "no child has same nom has master group for: {0}" -msgstr "pas d'enfant avec le nom du groupe maître pour {0} " +#: tiramisu/option.py:1335 +msgid "callback of master's option shall not refered a slave's ones" +msgstr "" +"callback d'une variable maitre ne devrait pas référencer des variables " +"esclaves" -#: tiramisu/option.py:1214 +#: tiramisu/option.py:1343 msgid "group_type: {0} not allowed" msgstr "group_type : {0} non autorisé" -#: tiramisu/option.py:1306 +#: tiramisu/option.py:1443 msgid "malformed requirements type for option: {0}, must be a dict" msgstr "" "type requirements malformé pour l'option : {0}, doit être un dictionnaire" -#: tiramisu/option.py:1323 +#: tiramisu/option.py:1460 msgid "" "malformed requirements for option: {0} require must have option, expected " "and action keys" @@ -329,110 +420,110 @@ msgstr "" "requirements malformé pour l'option : {0} l'exigence doit avoir les clefs " "option, expected et action" -#: tiramisu/option.py:1328 +#: tiramisu/option.py:1465 msgid "malformed requirements for option: {0} inverse must be boolean" msgstr "" "requirements mal formés pour l'option : {0} inverse doit être un booléen" -#: tiramisu/option.py:1332 +#: tiramisu/option.py:1469 msgid "malformed requirements for option: {0} transitive must be boolean" msgstr "" "requirements mal formés pour l'option : {0} transitive doit être booléen" -#: tiramisu/option.py:1336 +#: tiramisu/option.py:1473 msgid "malformed requirements for option: {0} same_action must be boolean" msgstr "" "requirements mal formés pour l'option : {0} same_action doit être un booléen" -#: tiramisu/option.py:1340 +#: tiramisu/option.py:1477 msgid "malformed requirements must be an option in option {0}" msgstr "requirements mal formés doit être une option dans l'option {0}" -#: tiramisu/option.py:1343 -msgid "malformed requirements option {0} should not be a multi" +#: tiramisu/option.py:1480 +msgid "malformed requirements option {0} must not be a multi" msgstr "requirements mal formés l'option {0} ne doit pas être une multi" -#: tiramisu/option.py:1349 +#: tiramisu/option.py:1486 msgid "" "malformed requirements second argument must be valid for option {0}: {1}" msgstr "" "requirements mal formés deuxième argument doit être valide pour l'option " "{0} : {1}" -#: tiramisu/option.py:1354 +#: tiramisu/option.py:1491 msgid "inconsistency in action types for option: {0} action: {1}" msgstr "incohérence dans les types action pour l'option : {0} action {1}" -#: tiramisu/option.py:1379 -msgid "{0} should be a function" +#: tiramisu/option.py:1516 +msgid "{0} must be a function" msgstr "{0} doit être une fonction" -#: tiramisu/option.py:1382 -msgid "{0}_params should be a dict" -msgstr "{0}_params devrait être un dict" +#: tiramisu/option.py:1519 +msgid "{0}_params must be a dict" +msgstr "{0}_params doit être un dict" -#: tiramisu/option.py:1385 -msgid "{0}_params with key {1} should not have length different to 1" +#: tiramisu/option.py:1522 +msgid "{0}_params with key {1} mustn't have length different to 1" msgstr "" -"{0}_params avec la clef {1} devrait ne pas avoir une longueur différent de 1" +"{0}_params avec la clef {1} ne doit pas avoir une longueur différent de 1" -#: tiramisu/option.py:1389 -msgid "{0}_params should be tuple for key \"{1}\"" -msgstr "{0}_params devrait être un tuple pour la clef \"{1}\"" +#: tiramisu/option.py:1526 +msgid "{0}_params must be tuple for key \"{1}\"" +msgstr "{0}_params doit être un tuple pour la clef \"{1}\"" -#: tiramisu/option.py:1395 +#: tiramisu/option.py:1532 msgid "validator not support tuple" msgstr "validator n'accepte pas de tuple" -#: tiramisu/option.py:1398 -msgid "{0}_params should have an option not a {0} for first argument" -msgstr "{0}_params devrait avoir une option pas un {0} pour premier argument" +#: tiramisu/option.py:1535 +msgid "{0}_params must have an option not a {0} for first argument" +msgstr "{0}_params doit avoir une option pas un {0} pour premier argument" -#: tiramisu/option.py:1402 -msgid "{0}_params should have a boolean not a {0} for second argument" -msgstr "{0}_params devrait avoir un boolean pas un {0} pour second argument" +#: tiramisu/option.py:1539 +msgid "{0}_params must have a boolean not a {0} for second argument" +msgstr "{0}_params doit avoir un booléen pas un {0} pour second argument" -#: tiramisu/setting.py:111 +#: tiramisu/setting.py:116 msgid "can't rebind {0}" msgstr "ne peut redéfinir ({0})" -#: tiramisu/setting.py:116 +#: tiramisu/setting.py:121 msgid "can't unbind {0}" msgstr "ne peut supprimer ({0})" -#: tiramisu/setting.py:254 +#: tiramisu/setting.py:272 msgid "cannot append {0} property for option {1}: this property is calculated" msgstr "" "ne peut ajouter la propriété {0} dans l'option {1}: cette propriété est " "calculée" -#: tiramisu/setting.py:317 +#: tiramisu/setting.py:363 msgid "opt and all_properties must not be set together in reset" msgstr "opt et all_properties ne doit pas être renseigné ensemble dans reset" -#: tiramisu/setting.py:332 +#: tiramisu/setting.py:378 msgid "if opt is not None, path should not be None in _getproperties" msgstr "" "si opt n'est pas None, path devrait ne pas être à None dans _getproperties" -#: tiramisu/setting.py:435 +#: tiramisu/setting.py:487 msgid "cannot change the value for option {0} this option is frozen" msgstr "" "ne peut modifier la valeur de l'option {0} cette option n'est pas modifiable" -#: tiramisu/setting.py:441 +#: tiramisu/setting.py:493 msgid "trying to access to an option named: {0} with properties {1}" msgstr "tentative d'accès à une option nommée : {0} avec les propriétés {1}" -#: tiramisu/setting.py:459 +#: tiramisu/setting.py:511 msgid "permissive must be a tuple" msgstr "permissive doit être un tuple" -#: tiramisu/setting.py:466 tiramisu/value.py:299 +#: tiramisu/setting.py:518 tiramisu/value.py:334 msgid "invalid generic owner {0}" msgstr "invalide owner générique {0}" -#: tiramisu/setting.py:553 +#: tiramisu/setting.py:606 msgid "" "malformed requirements imbrication detected for option: '{0}' with " "requirement on: '{1}'" @@ -440,75 +531,119 @@ msgstr "" "imbrication de requirements mal formés detectée pour l'option : '{0}' avec " "requirement sur : '{1}'" -#: tiramisu/setting.py:565 +#: tiramisu/setting.py:617 msgid "option '{0}' has requirement's property error: {1} {2}" msgstr "l'option '{0}' a une erreur de propriété pour le requirement : {1} {2}" -#: tiramisu/storage/__init__.py:47 +#: tiramisu/storage/__init__.py:52 msgid "storage_type is already set, cannot rebind it" msgstr "storage_type est déjà défini, impossible de le redéfinir" -#: tiramisu/storage/__init__.py:81 +#: tiramisu/storage/__init__.py:86 msgid "option {0} not already exists in storage {1}" msgstr "option {0} n'existe pas dans l'espace de stockage {1}" -#: tiramisu/storage/dictionary/storage.py:37 +#: tiramisu/storage/dictionary/storage.py:39 msgid "dictionary storage cannot delete session" msgstr "" "impossible de supprimer une session dans un espace de stockage dictionary" -#: tiramisu/storage/dictionary/storage.py:48 +#: tiramisu/storage/dictionary/storage.py:50 msgid "session already used" msgstr "session déjà utilisée" -#: tiramisu/storage/dictionary/storage.py:50 +#: tiramisu/storage/dictionary/storage.py:52 msgid "a dictionary cannot be persistent" msgstr "un espace de stockage dictionary ne peut être persistant" -#: tiramisu/value.py:306 +#: tiramisu/value.py:341 msgid "no value for {0} cannot change owner to {1}" msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {1}" -#: tiramisu/value.py:414 +#: tiramisu/value.py:423 +msgid "can force cache only if cache is actived in config" +msgstr "" +"peut force la mise en cache seulement si le cache est activé dans la config" + +#: tiramisu/value.py:462 +msgid "{0} is already a Multi " +msgstr "{0} est déjà une Multi" + +#: tiramisu/value.py:498 tiramisu/value.py:562 msgid "invalid len for the slave: {0} which has {1} as master" msgstr "longueur invalide pour une esclave : {0} qui a {1} comme maître" -#: tiramisu/value.py:438 -msgid "invalid len for the master: {0} which has {1} as slave with greater len" -msgstr "" -"longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus " -"grande longueur" - -#: tiramisu/value.py:468 +#: tiramisu/value.py:534 msgid "cannot append a value on a multi option {0} which is a slave" msgstr "ne peut ajouter une valeur sur l'option multi {0} qui est une esclave" -#: tiramisu/value.py:505 +#: tiramisu/value.py:572 msgid "cannot sort multi option {0} if master or slave" msgstr "ne peut trier une option multi {0} pour une maître ou une esclave" -#: tiramisu/value.py:509 +#: tiramisu/value.py:576 msgid "cmp is not permitted in python v3 or greater" msgstr "cmp n'est pas permis en python v3 ou supérieure" -#: tiramisu/value.py:518 +#: tiramisu/value.py:585 msgid "cannot reverse multi option {0} if master or slave" msgstr "ne peut inverser une option multi {0} pour une maître ou une esclave" -#: tiramisu/value.py:526 +#: tiramisu/value.py:593 msgid "cannot insert multi option {0} if master or slave" msgstr "ne peut insérer une option multi {0} pour une maître ou une esclave" -#: tiramisu/value.py:534 +#: tiramisu/value.py:601 msgid "cannot extend multi option {0} if master or slave" msgstr "ne peut étendre une option multi {0} pour une maître ou une esclave" -#: tiramisu/value.py:562 +#: tiramisu/value.py:612 +msgid "invalid value {0} for option {1}: {2}" +msgstr "valeur invalide {0} pour l'option {1} : {2}" + +#: tiramisu/value.py:630 msgid "cannot pop a value on a multi option {0} which is a slave" msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave" -#~ msgid "invalid value {0} for option {1} which must be a list" -#~ msgstr "valeur invalide {0} pour l'option {1} qui doit être une liste" +#~ msgid "option not in all_cons_opts" +#~ msgstr "option non présentante dans all_cons_opts" + +#~ msgid "invalid network {0} ({1}) with netmask {2}, this network is an IP" +#~ msgstr "réseau invalide {0} ({1}) avec masque {2}, ce réseau est une IP" + +#~ msgid "invalid IP {0} ({1}) with netmask {2}" +#~ msgstr "IP invalide {0} ({1}) avec masque {2}" + +#~ msgid "" +#~ "invalid len for the master: {0} which has {1} as slave with greater len" +#~ msgstr "" +#~ "longueur invalide pour un maître : {0} qui a {1} une esclave avec une " +#~ "plus grande longueur" + +#~ msgid "" +#~ "unable to carry out a calculation, option value with multi types must " +#~ "have same length for: {0}" +#~ msgstr "" +#~ "impossible d'effectuer le calcul, la valeur d'une option avec le type " +#~ "multi doit avoir la même longueur pour : {0}" + +#~ msgid "no child has same nom has master group for: {0}" +#~ msgstr "pas d'enfant avec le nom du groupe maître pour {0} " + +#~ msgid "value must be a boolean" +#~ msgstr "valeur doit être un booléen" + +#~ msgid "value must be an integer" +#~ msgstr "valeur doit être un nombre entier" + +#~ msgid "value must be a float" +#~ msgstr "valeur doit être un nombre flottant" + +#~ msgid "value must be a string, not {0}" +#~ msgstr "valeur doit être une chaîne, pas {0}" + +#~ msgid "value must be an unicode" +#~ msgstr "valeur doit être une valeur unicode" #~ msgid "invalid value {0} for option {1} must be different as {2} option" #~ msgstr "" @@ -532,17 +667,6 @@ msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave" #~ msgid "invalid name: {0} for optiondescription" #~ msgstr "nom invalide : {0} pour l'optiondescription" -#~ msgid "metaconfig's children must be config, not {0}" -#~ msgstr "enfants d'une metaconfig doit être une config, pas {0}" - -#~ msgid "all config in metaconfig must have same optiondescription" -#~ msgstr "" -#~ "toutes les configs d'une metaconfig doivent avoir la même " -#~ "optiondescription" - -#~ msgid "child has already a metaconfig's" -#~ msgstr "enfant a déjà une metaconfig" - #~ msgid "not allowed group_type : {0}" #~ msgstr "group_type non autorisé : {0}" diff --git a/translations/tiramisu.pot b/translations/tiramisu.pot index 57b90e8..81a388e 100644 --- a/translations/tiramisu.pot +++ b/translations/tiramisu.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-28 19:06+CEST\n" +"POT-Creation-Date: 2014-03-12 21:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,43 +15,44 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: tiramisu/autolib.py:144 +#: tiramisu/autolib.py:162 msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}" msgstr "" -#: tiramisu/autolib.py:153 -msgid "unable to carry out a calculation, option value with multi types must have same length for: {0}" -msgstr "" - -#: tiramisu/config.py:51 +#: tiramisu/config.py:52 msgid "descr must be an optiondescription, not {0}" msgstr "" -#: tiramisu/config.py:126 +#: tiramisu/config.py:127 msgid "unknown group_type: {0}" msgstr "" -#: tiramisu/config.py:162 -msgid "no option description found for this config (may be metaconfig without meta)" +#: tiramisu/config.py:164 tiramisu/setting.py:339 tiramisu/value.py:57 +#: tiramisu/value.py:485 +msgid "the context does not exist anymore" msgstr "" -#: tiramisu/config.py:188 +#: tiramisu/config.py:169 +msgid "no option description found for this config (may be GroupConfig)" +msgstr "" + +#: tiramisu/config.py:195 msgid "can't assign to an OptionDescription" msgstr "" -#: tiramisu/config.py:319 +#: tiramisu/config.py:325 msgid "unknown type_ type {0}for _find" msgstr "" -#: tiramisu/config.py:358 +#: tiramisu/config.py:364 msgid "no option found in config with these criteria" msgstr "" -#: tiramisu/config.py:408 +#: tiramisu/config.py:414 msgid "make_dict can't filtering with value without option" msgstr "" -#: tiramisu/config.py:429 +#: tiramisu/config.py:435 msgid "unexpected path {0}, should start with {1}" msgstr "" @@ -59,411 +60,507 @@ msgstr "" msgid "opt in getowner must be an option not {0}" msgstr "" -#: tiramisu/option.py:68 +#: tiramisu/config.py:532 +msgid "cannot serialize Config with MetaConfig" +msgstr "" + +#: tiramisu/config.py:546 +msgid "this storage is not serialisable, could be a none persistent storage" +msgstr "" + +#: tiramisu/config.py:609 +msgid "metaconfig's children must be a list" +msgstr "" + +#: tiramisu/config.py:703 +msgid "metaconfig's children should be config, not {0}" +msgstr "" + +#: tiramisu/config.py:707 +msgid "child has already a metaconfig's" +msgstr "" + +#: tiramisu/config.py:711 +msgid "all config in metaconfig must have the same optiondescription" +msgstr "" + +#: tiramisu/option.py:67 msgid "invalid name: {0} for option" msgstr "" -#: tiramisu/option.py:77 +#: tiramisu/option.py:76 msgid "invalid properties type {0} for {1}, must be a tuple" msgstr "" -#: tiramisu/option.py:115 +#: tiramisu/option.py:114 msgid "'{0}' ({1}) object attribute '{2}' is read-only" msgstr "" -#: tiramisu/option.py:142 tiramisu/value.py:360 +#: tiramisu/option.py:141 tiramisu/value.py:395 msgid "information's item not found: {0}" msgstr "" -#: tiramisu/option.py:204 +#: tiramisu/option.py:203 msgid "cannot serialize Option, only in OptionDescription" msgstr "" -#: tiramisu/option.py:307 +#: tiramisu/option.py:306 msgid "a default_multi is set whereas multi is False in option: {0}" msgstr "" -#: tiramisu/option.py:313 +#: tiramisu/option.py:312 msgid "invalid default_multi value {0} for option {1}: {2}" msgstr "" -#: tiramisu/option.py:318 +#: tiramisu/option.py:317 msgid "default value not allowed if option: {0} is calculated" msgstr "" -#: tiramisu/option.py:321 +#: tiramisu/option.py:320 msgid "params defined for a callback function but no callback defined yet for option {0}" msgstr "" -#: tiramisu/option.py:360 -msgid "option not in all_cons_opts" +#: tiramisu/option.py:425 tiramisu/option.py:450 +msgid "invalid value for option {0}: {1}" msgstr "" -#: tiramisu/option.py:432 tiramisu/value.py:545 -msgid "invalid value {0} for option {1}: {2}" +#: tiramisu/option.py:444 +msgid "warning on the value of the option {0}: {1}" msgstr "" -#: tiramisu/option.py:449 -msgid "which must be a list" +#: tiramisu/option.py:461 +msgid "invalid value {0} for option {1} which must be a list" msgstr "" -#: tiramisu/option.py:509 -msgid "consistency should be set with an option" +#: tiramisu/option.py:519 +msgid "consistency must be set with an option" msgstr "" -#: tiramisu/option.py:511 +#: tiramisu/option.py:521 msgid "cannot add consistency with itself" msgstr "" -#: tiramisu/option.py:513 -msgid "every options in consistency should be multi or none" +#: tiramisu/option.py:523 +msgid "every options in consistency must be multi or none" msgstr "" -#: tiramisu/option.py:533 -msgid "same value for {0} and {1}" +#: tiramisu/option.py:544 +msgid "same value for {0} and {1}, should be different" msgstr "" -#: tiramisu/option.py:642 +#: tiramisu/option.py:546 +msgid "same value for {0} and {1}, must be different" +msgstr "" + +#: tiramisu/option.py:640 msgid "values must be a tuple for {0}" msgstr "" -#: tiramisu/option.py:645 +#: tiramisu/option.py:643 msgid "open_values must be a boolean for {0}" msgstr "" -#: tiramisu/option.py:667 +#: tiramisu/option.py:665 msgid "value {0} is not permitted, only {1} is allowed" msgstr "" -#: tiramisu/option.py:679 -msgid "value must be a boolean" +#: tiramisu/option.py:677 +msgid "invalid boolean" msgstr "" -#: tiramisu/option.py:689 -msgid "value must be an integer" +#: tiramisu/option.py:687 +msgid "invalid integer" msgstr "" -#: tiramisu/option.py:699 -msgid "value must be a float" +#: tiramisu/option.py:697 +msgid "invalid float" msgstr "" -#: tiramisu/option.py:709 -msgid "value must be a string, not {0}" +#: tiramisu/option.py:707 +msgid "invalid string" msgstr "" -#: tiramisu/option.py:727 -msgid "value must be an unicode" +#: tiramisu/option.py:724 +msgid "invalid unicode" msgstr "" -#: tiramisu/option.py:739 +#: tiramisu/option.py:736 msgid "malformed symlinkoption must be an option for symlink {0}" msgstr "" -#: tiramisu/option.py:788 -msgid "invalid IP {0}" +#: tiramisu/option.py:787 tiramisu/option.py:790 tiramisu/option.py:795 +msgid "invalid IP" msgstr "" -#: tiramisu/option.py:793 -msgid "IP mustn't not be in reserved class" +#: tiramisu/option.py:801 +msgid "IP shouldn't be in reserved class" msgstr "" -#: tiramisu/option.py:795 -msgid "IP must be in private class" +#: tiramisu/option.py:803 +msgid "invalid IP, mustn't be in reserved class" msgstr "" -#: tiramisu/option.py:833 -msgid "inconsistency in allowed range" +#: tiramisu/option.py:807 +msgid "IP should be in private class" msgstr "" -#: tiramisu/option.py:838 -msgid "max value is empty" +#: tiramisu/option.py:809 +msgid "invalid IP, must be in private class" msgstr "" -#: tiramisu/option.py:877 -msgid "invalid network address {0}" -msgstr "" - -#: tiramisu/option.py:882 -msgid "network shall not be in reserved class" -msgstr "" - -#: tiramisu/option.py:894 -msgid "invalid netmask address {0}" -msgstr "" - -#: tiramisu/option.py:910 -msgid "invalid len for opts" -msgstr "" - -#: tiramisu/option.py:922 -msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" -msgstr "" - -#: tiramisu/option.py:927 -msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" -msgstr "" - -#: tiramisu/option.py:932 -msgid "invalid IP {0} ({1}) with netmask {2} ({3})" -msgstr "" - -#: tiramisu/option.py:934 -msgid "invalid network {0} ({1}) with netmask {2} ({3})" -msgstr "" - -#: tiramisu/option.py:948 -msgid "invalid broadcast address {0}" -msgstr "" - -#: tiramisu/option.py:952 +#: tiramisu/option.py:814 tiramisu/option.py:989 msgid "invalid len for vals" msgstr "" -#: tiramisu/option.py:957 +#: tiramisu/option.py:820 +msgid "IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})" +msgstr "" + +#: tiramisu/option.py:823 +msgid "invalid IP {0} ({1}) not in network {2} ({3}) with netmask {4} ({5})" +msgstr "" + +#: tiramisu/option.py:864 +msgid "inconsistency in allowed range" +msgstr "" + +#: tiramisu/option.py:869 +msgid "max value is empty" +msgstr "" + +#: tiramisu/option.py:886 +msgid "invalid port, range must have two values only" +msgstr "" + +#: tiramisu/option.py:889 +msgid "invalid port, first port in range must be smaller than the second one" +msgstr "" + +#: tiramisu/option.py:898 +msgid "invalid port" +msgstr "" + +#: tiramisu/option.py:900 +msgid "invalid port, must be an between {0} and {1}" +msgstr "" + +#: tiramisu/option.py:914 +msgid "invalid network address" +msgstr "" + +#: tiramisu/option.py:920 +msgid "network address shouldn't be in reserved class" +msgstr "" + +#: tiramisu/option.py:922 +msgid "invalid network address, mustn't be in reserved class" +msgstr "" + +#: tiramisu/option.py:935 +msgid "invalid netmask address" +msgstr "" + +#: tiramisu/option.py:952 +msgid "invalid len for opts" +msgstr "" + +#: tiramisu/option.py:966 +msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network" +msgstr "" + +#: tiramisu/option.py:971 +msgid "invalid network {0} ({1}) with netmask {2}" +msgstr "" + +#: tiramisu/option.py:985 +msgid "invalid broadcast address" +msgstr "" + +#: tiramisu/option.py:994 msgid "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})" msgstr "" -#: tiramisu/option.py:979 +#: tiramisu/option.py:1016 msgid "unknown type_ {0} for hostname" msgstr "" -#: tiramisu/option.py:982 +#: tiramisu/option.py:1019 msgid "allow_ip must be a boolean" msgstr "" -#: tiramisu/option.py:1012 -msgid "invalid value for {0}, must have dot" +#: tiramisu/option.py:1021 +msgid "allow_without_dot must be a boolean" msgstr "" -#: tiramisu/option.py:1015 -msgid "invalid domainname's length for {0} (max {1})" -msgstr "" - -#: tiramisu/option.py:1018 -msgid "invalid domainname's length for {0} (min 2)" -msgstr "" - -#: tiramisu/option.py:1022 -msgid "invalid domainname" -msgstr "" - -#: tiramisu/option.py:1049 -msgid "duplicate option name: {0}" +#: tiramisu/option.py:1065 +msgid "invalid domainname, must have dot" msgstr "" #: tiramisu/option.py:1067 -msgid "unknown Option {0} in OptionDescription {1}" +msgid "invalid domainname's length (max 255)" msgstr "" -#: tiramisu/option.py:1118 -msgid "duplicate option: {0}" +#: tiramisu/option.py:1069 +msgid "invalid domainname's length (min 2)" +msgstr "" + +#: tiramisu/option.py:1071 +msgid "invalid domainname" +msgstr "" + +#: tiramisu/option.py:1084 +msgid "invalid email address, must contains one @" +msgstr "" + +#: tiramisu/option.py:1087 +msgid "invalid username in email address" +msgstr "" + +#: tiramisu/option.py:1100 +msgid "invalid url, must start with http:// or https://" +msgstr "" + +#: tiramisu/option.py:1119 +msgid "invalid url, port must be an between 0 and 65536" +msgstr "" + +#: tiramisu/option.py:1125 +msgid "invalid url, must ends with filename" +msgstr "" + +#: tiramisu/option.py:1137 +msgid "invalid username" msgstr "" #: tiramisu/option.py:1148 +msgid "invalid filename" +msgstr "" + +#: tiramisu/option.py:1175 +msgid "duplicate option name: {0}" +msgstr "" + +#: tiramisu/option.py:1193 +msgid "unknown Option {0} in OptionDescription {1}" +msgstr "" + +#: tiramisu/option.py:1244 +msgid "duplicate option: {0}" +msgstr "" + +#: tiramisu/option.py:1275 msgid "consistency with option {0} which is not in Config" msgstr "" -#: tiramisu/option.py:1156 +#: tiramisu/option.py:1283 msgid "no option for path {0}" msgstr "" -#: tiramisu/option.py:1162 +#: tiramisu/option.py:1289 msgid "no option {0} found" msgstr "" -#: tiramisu/option.py:1172 +#: tiramisu/option.py:1299 msgid "cannot change group_type if already set (old {0}, new {1})" msgstr "" -#: tiramisu/option.py:1185 +#: tiramisu/option.py:1311 msgid "master group {0} shall not have a subgroup" msgstr "" -#: tiramisu/option.py:1188 +#: tiramisu/option.py:1314 msgid "master group {0} shall not have a symlinkoption" msgstr "" -#: tiramisu/option.py:1191 +#: tiramisu/option.py:1317 msgid "not allowed option {0} in group {1}: this option is not a multi" msgstr "" -#: tiramisu/option.py:1202 +#: tiramisu/option.py:1327 msgid "master group with wrong master name for {0}" msgstr "" -#: tiramisu/option.py:1211 -msgid "no child has same nom has master group for: {0}" -msgstr "" - -#: tiramisu/option.py:1214 -msgid "group_type: {0} not allowed" -msgstr "" - -#: tiramisu/option.py:1306 -msgid "malformed requirements type for option: {0}, must be a dict" -msgstr "" - -#: tiramisu/option.py:1323 -msgid "malformed requirements for option: {0} require must have option, expected and action keys" -msgstr "" - -#: tiramisu/option.py:1328 -msgid "malformed requirements for option: {0} inverse must be boolean" -msgstr "" - -#: tiramisu/option.py:1332 -msgid "malformed requirements for option: {0} transitive must be boolean" -msgstr "" - -#: tiramisu/option.py:1336 -msgid "malformed requirements for option: {0} same_action must be boolean" -msgstr "" - -#: tiramisu/option.py:1340 -msgid "malformed requirements must be an option in option {0}" +#: tiramisu/option.py:1335 +msgid "callback of master's option shall not refered a slave's ones" msgstr "" #: tiramisu/option.py:1343 -msgid "malformed requirements option {0} should not be a multi" +msgid "group_type: {0} not allowed" msgstr "" -#: tiramisu/option.py:1349 +#: tiramisu/option.py:1443 +msgid "malformed requirements type for option: {0}, must be a dict" +msgstr "" + +#: tiramisu/option.py:1460 +msgid "malformed requirements for option: {0} require must have option, expected and action keys" +msgstr "" + +#: tiramisu/option.py:1465 +msgid "malformed requirements for option: {0} inverse must be boolean" +msgstr "" + +#: tiramisu/option.py:1469 +msgid "malformed requirements for option: {0} transitive must be boolean" +msgstr "" + +#: tiramisu/option.py:1473 +msgid "malformed requirements for option: {0} same_action must be boolean" +msgstr "" + +#: tiramisu/option.py:1477 +msgid "malformed requirements must be an option in option {0}" +msgstr "" + +#: tiramisu/option.py:1480 +msgid "malformed requirements option {0} must not be a multi" +msgstr "" + +#: tiramisu/option.py:1486 msgid "malformed requirements second argument must be valid for option {0}: {1}" msgstr "" -#: tiramisu/option.py:1354 +#: tiramisu/option.py:1491 msgid "inconsistency in action types for option: {0} action: {1}" msgstr "" -#: tiramisu/option.py:1379 -msgid "{0} should be a function" +#: tiramisu/option.py:1516 +msgid "{0} must be a function" msgstr "" -#: tiramisu/option.py:1382 -msgid "{0}_params should be a dict" +#: tiramisu/option.py:1519 +msgid "{0}_params must be a dict" msgstr "" -#: tiramisu/option.py:1385 -msgid "{0}_params with key {1} should not have length different to 1" +#: tiramisu/option.py:1522 +msgid "{0}_params with key {1} mustn't have length different to 1" msgstr "" -#: tiramisu/option.py:1389 -msgid "{0}_params should be tuple for key \"{1}\"" +#: tiramisu/option.py:1526 +msgid "{0}_params must be tuple for key \"{1}\"" msgstr "" -#: tiramisu/option.py:1395 +#: tiramisu/option.py:1532 msgid "validator not support tuple" msgstr "" -#: tiramisu/option.py:1398 -msgid "{0}_params should have an option not a {0} for first argument" +#: tiramisu/option.py:1535 +msgid "{0}_params must have an option not a {0} for first argument" msgstr "" -#: tiramisu/option.py:1402 -msgid "{0}_params should have a boolean not a {0} for second argument" -msgstr "" - -#: tiramisu/setting.py:111 -msgid "can't rebind {0}" +#: tiramisu/option.py:1539 +msgid "{0}_params must have a boolean not a {0} for second argument" msgstr "" #: tiramisu/setting.py:116 +msgid "can't rebind {0}" +msgstr "" + +#: tiramisu/setting.py:121 msgid "can't unbind {0}" msgstr "" -#: tiramisu/setting.py:254 +#: tiramisu/setting.py:272 msgid "cannot append {0} property for option {1}: this property is calculated" msgstr "" -#: tiramisu/setting.py:317 +#: tiramisu/setting.py:363 msgid "opt and all_properties must not be set together in reset" msgstr "" -#: tiramisu/setting.py:332 +#: tiramisu/setting.py:378 msgid "if opt is not None, path should not be None in _getproperties" msgstr "" -#: tiramisu/setting.py:435 +#: tiramisu/setting.py:487 msgid "cannot change the value for option {0} this option is frozen" msgstr "" -#: tiramisu/setting.py:441 +#: tiramisu/setting.py:493 msgid "trying to access to an option named: {0} with properties {1}" msgstr "" -#: tiramisu/setting.py:459 +#: tiramisu/setting.py:511 msgid "permissive must be a tuple" msgstr "" -#: tiramisu/setting.py:466 tiramisu/value.py:299 +#: tiramisu/setting.py:518 tiramisu/value.py:334 msgid "invalid generic owner {0}" msgstr "" -#: tiramisu/setting.py:553 +#: tiramisu/setting.py:606 msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'" msgstr "" -#: tiramisu/setting.py:565 +#: tiramisu/setting.py:617 msgid "option '{0}' has requirement's property error: {1} {2}" msgstr "" -#: tiramisu/storage/__init__.py:47 +#: tiramisu/storage/__init__.py:52 msgid "storage_type is already set, cannot rebind it" msgstr "" -#: tiramisu/storage/__init__.py:81 +#: tiramisu/storage/__init__.py:86 msgid "option {0} not already exists in storage {1}" msgstr "" -#: tiramisu/storage/dictionary/storage.py:37 +#: tiramisu/storage/dictionary/storage.py:39 msgid "dictionary storage cannot delete session" msgstr "" -#: tiramisu/storage/dictionary/storage.py:48 +#: tiramisu/storage/dictionary/storage.py:50 msgid "session already used" msgstr "" -#: tiramisu/storage/dictionary/storage.py:50 +#: tiramisu/storage/dictionary/storage.py:52 msgid "a dictionary cannot be persistent" msgstr "" -#: tiramisu/value.py:306 +#: tiramisu/value.py:341 msgid "no value for {0} cannot change owner to {1}" msgstr "" -#: tiramisu/value.py:414 +#: tiramisu/value.py:423 +msgid "can force cache only if cache is actived in config" +msgstr "" + +#: tiramisu/value.py:462 +msgid "{0} is already a Multi " +msgstr "" + +#: tiramisu/value.py:498 tiramisu/value.py:562 msgid "invalid len for the slave: {0} which has {1} as master" msgstr "" -#: tiramisu/value.py:438 -msgid "invalid len for the master: {0} which has {1} as slave with greater len" -msgstr "" - -#: tiramisu/value.py:468 +#: tiramisu/value.py:534 msgid "cannot append a value on a multi option {0} which is a slave" msgstr "" -#: tiramisu/value.py:505 +#: tiramisu/value.py:572 msgid "cannot sort multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:509 +#: tiramisu/value.py:576 msgid "cmp is not permitted in python v3 or greater" msgstr "" -#: tiramisu/value.py:518 +#: tiramisu/value.py:585 msgid "cannot reverse multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:526 +#: tiramisu/value.py:593 msgid "cannot insert multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:534 +#: tiramisu/value.py:601 msgid "cannot extend multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:562 +#: tiramisu/value.py:612 +msgid "invalid value {0} for option {1}: {2}" +msgstr "" + +#: tiramisu/value.py:630 msgid "cannot pop a value on a multi option {0} which is a slave" msgstr ""