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_config.py b/test/test_config.py index e091294..6db3fcf 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -9,7 +9,7 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ BoolOption, UnicodeOption, OptionDescription -from tiramisu.error import ConflictError +from tiramisu.error import ConflictError, ConfigError def make_description(): @@ -131,8 +131,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 +144,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 +152,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 +165,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 +237,60 @@ 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()') diff --git a/test/test_config_api.py b/test/test_config_api.py index ab4b484..db40c57 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -4,26 +4,31 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import IntOption, FloatOption, StrOption, ChoiceOption, \ - BoolOption, OptionDescription + BoolOption, FilenameOption, OptionDescription 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 @@ -100,20 +105,40 @@ 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')") def test_find_multi(): @@ -137,3 +162,18 @@ 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'") diff --git a/test/test_config_domain.py b/test/test_config_domain.py index da04235..f4579a6 100644 --- a/test/test_config_domain.py +++ b/test/test_config_domain.py @@ -2,23 +2,28 @@ 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) + od = OptionDescription('a', '', [d, f]) 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 = 'toto3.3la'") raises(ValueError, "c.d = '3toto.com'") - c.d = 'toto.co3' + 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' def test_domainname_netbios(): @@ -41,3 +46,33 @@ 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'") + + +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' 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_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..4db3410 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -8,6 +8,17 @@ from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ from tiramisu.error import ConfigError +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_not_equal(): a = IntOption('a', '') b = IntOption('b', '') diff --git a/test/test_option_owner.py b/test/test_option_owner.py index bc96285..b758e91 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(): @@ -52,6 +52,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_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_validator.py b/test/test_option_validator.py index 001f9f7..dc08c82 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for 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) == _('invalid value for option {0}: {1}').format('ip_admin_eth0', 'error') 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..4cf9372 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -26,6 +26,20 @@ def test_requires(): assert props == ['disabled'] +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(): a = BoolOption('activate_service', '', True) b = BoolOption('activate_service_web', '', True, @@ -504,6 +518,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 3188d3b..e7777f1 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -318,3 +318,46 @@ def test_state_groupconfig(): 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 efb7c0e..cb3552a 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -20,15 +20,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 @@ -49,13 +50,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') @@ -63,58 +64,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: @@ -133,29 +147,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: @@ -168,16 +189,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 = {} @@ -196,10 +210,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 @@ -213,7 +224,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 d024159..2c1a6b1 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -156,7 +156,15 @@ class SubConfig(object): __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: @@ -250,7 +258,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 @@ -262,11 +271,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 @@ -277,7 +286,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): @@ -734,4 +744,6 @@ def mandatory_warnings(config): except PropertiesOptionError as err: if err.proptype == ['mandatory']: yield path + except ConfigError: + pass config.cfgimpl_reset_cache(only=('values',)) diff --git a/tiramisu/option.py b/tiramisu/option.py index c7a28c2..ad99416 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -40,9 +40,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 \ @@ -413,7 +411,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) @@ -421,7 +419,11 @@ 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)) try: # valid with self._validator val_validator(_value) @@ -430,8 +432,8 @@ class Option(BaseOption): 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) + msg = _("invalid value for option {0}: {1}").format( + self._name, err) if self._warnings_only: warnings.warn_explicit(ValueWarning(msg, self), ValueWarning, @@ -447,17 +449,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" @@ -677,7 +675,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): @@ -687,7 +685,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): @@ -697,7 +695,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): @@ -707,12 +705,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 @@ -725,7 +722,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): @@ -783,17 +780,23 @@ 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 + for val in value.split('.'): + if val.startswith("0") and len(val) > 1: + 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): 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")) + raise ValueError(_("invalid IP, mustn't not be in reserved class")) if self._private_only and not ip.iptype() == 'PRIVATE': - raise ValueError(_("IP must be in private class")) + raise ValueError(_("invalid IP, must be in private class")) class PortOption(Option): @@ -853,16 +856,17 @@ 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 part, range must have two values ' + 'only') if not value[0] < value[1]: - raise ValueError('first port in range must be' + raise ValueError('invalid port, first port in range must be' ' smaller than the second one') else: value = [value] for val in value: if not self._min_value <= int(val) <= self._max_value: - raise ValueError('port must be an between {0} and {1}' + raise ValueError('invalid port, must be an between {0} and {1}' ''.format(self._min_value, self._max_value)) @@ -875,12 +879,12 @@ 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): ip = IP(value) if ip.iptype() == 'RESERVED': - raise ValueError(_("network shall not be in reserved class")) + raise ValueError(_("invalid network address, must not be in reserved class")) class NetmaskOption(Option): @@ -892,7 +896,7 @@ 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): #opts must be (netmask, network) options @@ -921,21 +925,21 @@ class NetmaskOption(Option): except ValueError: if not make_net: msg = _("invalid network {0} ({1}) " - "with netmask {2} ({3})," + "with netmask {2}," " this network is an IP") 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})") + msg = _('invalid IP {0} ({1}) with netmask {2}') else: - msg = _("invalid network {0} ({1}) with netmask {2} ({3})") + 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): @@ -946,7 +950,7 @@ 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): if len(vals) != 3: @@ -968,20 +972,39 @@ 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': + 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][a-z\d\-{0}]{{,{1}}}{2}){3}$' + ''.format(extrachar, length, extrachar_mandatory, end)) super(DomainnameOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, @@ -1000,29 +1023,82 @@ 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, should 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, should 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, should ends with filename')) + + +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`. @@ -1177,7 +1253,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 @@ -1194,7 +1269,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: @@ -1203,14 +1277,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)) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 684ec74..101b551 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -24,7 +24,7 @@ from time import time from copy import copy import weakref from tiramisu.error import (RequirementError, PropertiesOptionError, - ConstError) + ConstError, ConfigError) from tiramisu.i18n import _ @@ -242,6 +242,14 @@ multitypes = MultiTypeModule() populate_multitypes() +# ____________________________________________________________ +class Undefined(): + pass + + +undefined = Undefined() + + # ____________________________________________________________ class Property(object): "a property is responsible of the option's value access rules" @@ -254,6 +262,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}: ' @@ -263,12 +276,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): @@ -280,7 +310,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): @@ -298,6 +328,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): @@ -322,12 +363,12 @@ 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: @@ -380,7 +421,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, @@ -406,10 +447,12 @@ class Settings(object): """ # opt properties properties = copy(self._getproperties(opt_or_descr, path)) + self_properties = copy(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: @@ -426,7 +469,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: @@ -549,6 +592,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, \ @@ -560,8 +604,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 @@ -590,7 +633,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 1b7001b..899af4a 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -31,7 +31,7 @@ class Settings(Cache): self._permissives = {} super(Settings, self).__init__(storage) - # propertives + # properties def setproperties(self, path, properties): self._properties[path] = properties @@ -41,7 +41,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 ed79181..9a5d2f9 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -33,7 +33,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,), @@ -56,7 +56,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 4426742..863f2a1 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -22,7 +22,7 @@ from copy import copy import sys import weakref from tiramisu.error import ConfigError, SlaveError -from tiramisu.setting import owners, multitypes, expires_time +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 @@ -46,13 +46,24 @@ 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] else: @@ -62,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 @@ -71,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): @@ -105,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(): @@ -137,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) @@ -151,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()) @@ -176,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 @@ -186,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 @@ -196,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 = [] @@ -228,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) @@ -251,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): @@ -285,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 @@ -337,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): @@ -389,6 +420,8 @@ class Multi(list): :param opt: the option object that have this Multi value :param setitem: only if set a value """ + if isinstance(value, Multi): + raise ValueError(_('{0} is already a Multi ').format(opt._name)) self.opt = opt self.path = path if not isinstance(context, weakref.ReferenceType): @@ -398,21 +431,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)) @@ -429,59 +473,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) @@ -490,16 +518,15 @@ class Multi(list): dvalue = values._getcallback_value(slave, index=index) else: dvalue = slave.impl_getdefault_multi() - old_value = values.getitem(slave, path, + old_value = values.getitem(slave, path, validate=False, validate_properties=False) - if len(old_value) < self.__len__(): - values.getitem(slave, path, - validate_properties=False).append( - dvalue, force=True) - else: - values.getitem(slave, path, - validate_properties=False)[ - index] = dvalue + if len(old_value) + 1 != self.__len__(): + raise SlaveError(_("invalid len for the slave: {0}" + " which has {1} as master").format( + self.opt._name, self.__len__())) + values.getitem(slave, path, validate=False, + validate_properties=False).append( + dvalue, force=True) def sort(self, cmp=None, key=None, reverse=False): if self.opt.impl_get_multitype() in [multitypes.slave, @@ -512,7 +539,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, @@ -520,7 +547,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, @@ -528,7 +555,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, @@ -536,12 +563,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} " @@ -559,19 +586,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 93e30f4..83a9289 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -1,105 +1,105 @@ 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-01-25 11:30+CET\n" "PO-Revision-Date: \n" "Last-Translator: Emmanuel Garette \n" -"Language-Team: LANGUAGE \n" +"Language-Team: Tiramisu's team \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" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" -#: tiramisu/autolib.py:144 +#: tiramisu/autolib.py:159 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 +#: tiramisu/config.py:166 tiramisu/setting.py:339 tiramisu/value.py:57 +#: tiramisu/value.py:427 +msgid "the context does not exist anymore" +msgstr "le context n'existe plus" + +#: tiramisu/config.py:171 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:188 +#: tiramisu/config.py:197 msgid "can't assign to an OptionDescription" msgstr "ne peut pas attribuer une valeur à une OptionDescription" -#: tiramisu/config.py:319 +#: tiramisu/config.py:330 msgid "unknown type_ type {0}for _find" msgstr "type_ type {0} pour _find inconnu" -#: tiramisu/config.py:358 +#: tiramisu/config.py:369 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:419 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:440 msgid "unexpected path {0}, should start with {1}" msgstr "chemin imprévu {0}, devrait commencer par {1}" -#: tiramisu/config.py:489 +#: tiramisu/config.py:500 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/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:376 msgid "information's item not found: {0}" 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 +107,251 @@ msgstr "" "params définis pour une fonction callback mais par de callback encore " "définis pour l'option {0}" -#: tiramisu/option.py:360 +#: tiramisu/option.py:359 msgid "option not in all_cons_opts" msgstr "option non présentante dans all_cons_opts" -#: 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:425 tiramisu/option.py:435 +msgid "invalid value for option {0}: {1}" +msgstr "valeur invalide pour l'option {0} : {1}" -#: tiramisu/option.py:449 -msgid "which must be a list" -msgstr "lequel doit être une liste" +#: tiramisu/option.py:452 +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 +#: tiramisu/option.py:508 msgid "consistency should be set with an option" msgstr "consistency doit être configuré avec une option" -#: tiramisu/option.py:511 +#: tiramisu/option.py:510 msgid "cannot add consistency with itself" msgstr "ne peut ajouter une consistency avec lui même" -#: tiramisu/option.py:513 +#: tiramisu/option.py:512 msgid "every options in consistency should be multi or none" msgstr "" "toutes les options d'une consistency devrait être multi ou ne pas l'être" -#: tiramisu/option.py:533 +#: tiramisu/option.py:532 msgid "same value for {0} and {1}" msgstr "même valeur pour {0} et {1}" -#: tiramisu/option.py:642 +#: tiramisu/option.py:641 msgid "values must be a tuple for {0}" msgstr "values doit être un tuple pour {0}" -#: tiramisu/option.py:645 +#: tiramisu/option.py:644 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:666 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:678 +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:688 +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:698 +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:708 +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:725 +msgid "invalid unicode" +msgstr "invalide unicode" -#: tiramisu/option.py:739 +#: tiramisu/option.py:737 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:792 +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:797 +msgid "invalid IP, mustn't not be in reserved class" +msgstr "adresse IP invalide, ne doit pas être d'une classe reservée" -#: tiramisu/option.py:795 -msgid "IP must be in private class" -msgstr "IP doit être dans la classe privée" +#: tiramisu/option.py:799 +msgid "invalid IP, must be in private class" +msgstr "adresse IP invalide, doit être dans la classe privée" -#: tiramisu/option.py:833 +#: tiramisu/option.py:837 msgid "inconsistency in allowed range" msgstr "inconsistence dans la plage autorisée" -#: tiramisu/option.py:838 +#: tiramisu/option.py:842 msgid "max value is empty" msgstr "la valeur maximum est vide" -#: 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" +msgid "invalid network address" +msgstr "adresse réseau invalide" -#: tiramisu/option.py:894 -msgid "invalid netmask address {0}" -msgstr "masque de sous-réseau invalide {0}" +#: tiramisu/option.py:887 +msgid "invalid network address, must not be in reserved class" +msgstr "adresse réseau invalide, ne doit pas être dans la classe reservée" -#: tiramisu/option.py:910 +#: tiramisu/option.py:899 +msgid "invalid netmask address" +msgstr "masque de sous-réseau invalide" + +#: tiramisu/option.py:915 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" +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" #: tiramisu/option.py:932 -msgid "invalid IP {0} ({1}) with netmask {2} ({3})" -msgstr "IP invalide {0} ({1}) avec masque {2} ({3})" +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:934 -msgid "invalid network {0} ({1}) with netmask {2} ({3})" -msgstr "réseau invalide {0} ({1}) avec masque {2} ({3})" +#: tiramisu/option.py:937 +msgid "invalid IP {0} ({1}) with netmask {2}" +msgstr "IP invalide {0} ({1}) avec masque {2}" -#: tiramisu/option.py:948 -msgid "invalid broadcast address {0}" -msgstr "adresse de broadcast invalide {0}" +#: tiramisu/option.py:939 +msgid "invalid network {0} ({1}) with netmask {2}" +msgstr "réseau invalide {0} ({1}) avec masque {2}" -#: tiramisu/option.py:952 +#: tiramisu/option.py:953 +msgid "invalid broadcast address" +msgstr "adresse de broadcast invalide" + +#: tiramisu/option.py:957 msgid "invalid len for vals" msgstr "longueur invalide pour vals" -#: tiramisu/option.py:957 +#: tiramisu/option.py:962 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:984 msgid "unknown type_ {0} for hostname" msgstr "type_ inconnu {0} pour le nom d'hôte" -#: tiramisu/option.py:982 +#: tiramisu/option.py:987 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:989 +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:1028 +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:1030 +msgid "invalid domainname's length (max 255)" +msgstr "longueur du nom de domaine invalide (maximum {1})" -#: tiramisu/option.py:1022 +#: tiramisu/option.py:1032 +msgid "invalid domainname's length (min 2)" +msgstr "longueur du nom de domaine invalide (minimum 2)" + +#: tiramisu/option.py:1034 msgid "invalid domainname" msgstr "nom de domaine invalide" -#: tiramisu/option.py:1049 +#: tiramisu/option.py:1047 +msgid "invalid email address, should contains one @" +msgstr "adresse email invalide, devrait contenir un @" + +#: tiramisu/option.py:1050 +msgid "invalid username in email address" +msgstr "nom d'utilisateur invalide dans une adresse email" + +#: tiramisu/option.py:1063 +msgid "invalid url, should start with http:// or https://" +msgstr "URL invalide, devrait démarré avec http:// ou https://" + +#: tiramisu/option.py:1082 +msgid "invalid url, port must be an between 0 and 65536" +msgstr "URL invalide, port doit être entre 0 et 65536" + +#: tiramisu/option.py:1088 +msgid "invalid url, should ends with filename" +msgstr "URL invalide, devrait finir avec un nom de fichier" + +#: tiramisu/option.py:1099 +msgid "invalid filename" +msgstr "nom de fichier invalide" + +#: tiramisu/option.py:1126 msgid "duplicate option name: {0}" msgstr "nom de l'option dupliqué : {0}" -#: tiramisu/option.py:1067 +#: tiramisu/option.py:1144 msgid "unknown Option {0} in OptionDescription {1}" msgstr "Option {0} inconnue pour l'OptionDescription {1}" -#: tiramisu/option.py:1118 +#: tiramisu/option.py:1195 msgid "duplicate option: {0}" msgstr "option dupliquée : {0}" -#: tiramisu/option.py:1148 +#: tiramisu/option.py:1225 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:1233 msgid "no option for path {0}" msgstr "pas d'option pour le chemin {0}" -#: tiramisu/option.py:1162 +#: tiramisu/option.py:1239 msgid "no option {0} found" msgstr "pas d'option {0} trouvée" -#: tiramisu/option.py:1172 +#: tiramisu/option.py:1249 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:1261 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:1264 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:1267 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:1277 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:1285 +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:1293 msgid "group_type: {0} not allowed" msgstr "group_type : {0} non autorisé" -#: tiramisu/option.py:1306 +#: tiramisu/option.py:1385 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:1402 msgid "" "malformed requirements for option: {0} require must have option, expected " "and action keys" @@ -329,110 +359,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:1407 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:1411 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:1415 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:1419 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 +#: tiramisu/option.py:1422 msgid "malformed requirements option {0} should 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:1428 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:1433 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 +#: tiramisu/option.py:1458 msgid "{0} should be a function" msgstr "{0} doit être une fonction" -#: tiramisu/option.py:1382 +#: tiramisu/option.py:1461 msgid "{0}_params should be a dict" msgstr "{0}_params devrait être un dict" -#: tiramisu/option.py:1385 +#: tiramisu/option.py:1464 msgid "{0}_params with key {1} should not have length different to 1" msgstr "" "{0}_params avec la clef {1} devrait ne pas avoir une longueur différent de 1" -#: tiramisu/option.py:1389 +#: tiramisu/option.py:1468 msgid "{0}_params should be tuple for key \"{1}\"" msgstr "{0}_params devrait être un tuple pour la clef \"{1}\"" -#: tiramisu/option.py:1395 +#: tiramisu/option.py:1474 msgid "validator not support tuple" msgstr "validator n'accepte pas de tuple" -#: tiramisu/option.py:1398 +#: tiramisu/option.py:1477 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:1402 +#: tiramisu/option.py:1481 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/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:483 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:489 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:507 msgid "permissive must be a tuple" msgstr "permissive doit être un tuple" -#: tiramisu/setting.py:466 tiramisu/value.py:299 +#: tiramisu/setting.py:514 tiramisu/value.py:315 msgid "invalid generic owner {0}" msgstr "invalide owner générique {0}" -#: tiramisu/setting.py:553 +#: tiramisu/setting.py:602 msgid "" "malformed requirements imbrication detected for option: '{0}' with " "requirement on: '{1}'" @@ -440,75 +470,101 @@ msgstr "" "imbrication de requirements mal formés detectée pour l'option : '{0}' avec " "requirement sur : '{1}'" -#: tiramisu/setting.py:565 +#: tiramisu/setting.py:613 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:322 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:442 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 +#: tiramisu/value.py:466 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:496 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:535 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:539 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:548 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:556 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:564 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:575 +msgid "invalid value {0} for option {1}: {2}" +msgstr "valeur invalide {0} pour l'option {1} : {2}" + +#: tiramisu/value.py:593 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 "" +#~ "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 "" diff --git a/translations/tiramisu.pot b/translations/tiramisu.pot index 57b90e8..e572a40 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-01-25 11:30+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,455 +15,488 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: tiramisu/autolib.py:144 +#: tiramisu/autolib.py:159 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 +#: tiramisu/config.py:166 tiramisu/setting.py:339 tiramisu/value.py:57 +#: tiramisu/value.py:427 +msgid "the context does not exist anymore" +msgstr "" + +#: tiramisu/config.py:171 msgid "no option description found for this config (may be metaconfig without meta)" msgstr "" -#: tiramisu/config.py:188 +#: tiramisu/config.py:197 msgid "can't assign to an OptionDescription" msgstr "" -#: tiramisu/config.py:319 +#: tiramisu/config.py:330 msgid "unknown type_ type {0}for _find" msgstr "" -#: tiramisu/config.py:358 +#: tiramisu/config.py:369 msgid "no option found in config with these criteria" msgstr "" -#: tiramisu/config.py:408 +#: tiramisu/config.py:419 msgid "make_dict can't filtering with value without option" msgstr "" -#: tiramisu/config.py:429 +#: tiramisu/config.py:440 msgid "unexpected path {0}, should start with {1}" msgstr "" -#: tiramisu/config.py:489 +#: tiramisu/config.py:500 msgid "opt in getowner must be an option not {0}" msgstr "" -#: tiramisu/option.py:68 +#: 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:376 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 +#: tiramisu/option.py:359 msgid "option not in all_cons_opts" msgstr "" -#: tiramisu/option.py:432 tiramisu/value.py:545 -msgid "invalid value {0} for option {1}: {2}" +#: tiramisu/option.py:425 tiramisu/option.py:435 +msgid "invalid value for option {0}: {1}" msgstr "" -#: tiramisu/option.py:449 -msgid "which must be a list" +#: tiramisu/option.py:452 +msgid "invalid value {0} for option {1} which must be a list" msgstr "" -#: tiramisu/option.py:509 +#: tiramisu/option.py:508 msgid "consistency should be set with an option" msgstr "" -#: tiramisu/option.py:511 +#: tiramisu/option.py:510 msgid "cannot add consistency with itself" msgstr "" -#: tiramisu/option.py:513 +#: tiramisu/option.py:512 msgid "every options in consistency should be multi or none" msgstr "" -#: tiramisu/option.py:533 +#: tiramisu/option.py:532 msgid "same value for {0} and {1}" msgstr "" -#: tiramisu/option.py:642 +#: tiramisu/option.py:641 msgid "values must be a tuple for {0}" msgstr "" -#: tiramisu/option.py:645 +#: tiramisu/option.py:644 msgid "open_values must be a boolean for {0}" msgstr "" -#: tiramisu/option.py:667 +#: tiramisu/option.py:666 msgid "value {0} is not permitted, only {1} is allowed" msgstr "" -#: tiramisu/option.py:679 -msgid "value must be a boolean" +#: tiramisu/option.py:678 +msgid "invalid boolean" msgstr "" -#: tiramisu/option.py:689 -msgid "value must be an integer" +#: tiramisu/option.py:688 +msgid "invalid integer" msgstr "" -#: tiramisu/option.py:699 -msgid "value must be a float" +#: tiramisu/option.py:698 +msgid "invalid float" msgstr "" -#: tiramisu/option.py:709 -msgid "value must be a string, not {0}" +#: tiramisu/option.py:708 +msgid "invalid string" msgstr "" -#: tiramisu/option.py:727 -msgid "value must be an unicode" +#: tiramisu/option.py:725 +msgid "invalid unicode" msgstr "" -#: tiramisu/option.py:739 +#: tiramisu/option.py:737 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:792 +msgid "invalid IP" msgstr "" -#: tiramisu/option.py:793 -msgid "IP mustn't not be in reserved class" +#: tiramisu/option.py:797 +msgid "invalid IP, mustn't not be in reserved class" msgstr "" -#: tiramisu/option.py:795 -msgid "IP must be in private class" +#: tiramisu/option.py:799 +msgid "invalid IP, must be in private class" msgstr "" -#: tiramisu/option.py:833 +#: tiramisu/option.py:837 msgid "inconsistency in allowed range" msgstr "" -#: tiramisu/option.py:838 +#: tiramisu/option.py:842 msgid "max value is empty" msgstr "" -#: tiramisu/option.py:877 -msgid "invalid network address {0}" -msgstr "" - #: tiramisu/option.py:882 -msgid "network shall not be in reserved class" +msgid "invalid network address" msgstr "" -#: tiramisu/option.py:894 -msgid "invalid netmask address {0}" +#: tiramisu/option.py:887 +msgid "invalid network address, must not be in reserved class" msgstr "" -#: tiramisu/option.py:910 +#: tiramisu/option.py:899 +msgid "invalid netmask address" +msgstr "" + +#: tiramisu/option.py:915 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" +msgid "invalid network {0} ({1}) with netmask {2}, this network is an IP" msgstr "" #: tiramisu/option.py:932 -msgid "invalid IP {0} ({1}) with netmask {2} ({3})" +msgid "invalid IP {0} ({1}) with netmask {2}, this IP is a network" msgstr "" -#: tiramisu/option.py:934 -msgid "invalid network {0} ({1}) with netmask {2} ({3})" +#: tiramisu/option.py:937 +msgid "invalid IP {0} ({1}) with netmask {2}" msgstr "" -#: tiramisu/option.py:948 -msgid "invalid broadcast address {0}" +#: tiramisu/option.py:939 +msgid "invalid network {0} ({1}) with netmask {2}" msgstr "" -#: tiramisu/option.py:952 -msgid "invalid len for vals" +#: tiramisu/option.py:953 +msgid "invalid broadcast address" msgstr "" #: tiramisu/option.py:957 +msgid "invalid len for vals" +msgstr "" + +#: tiramisu/option.py:962 msgid "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})" msgstr "" -#: tiramisu/option.py:979 +#: tiramisu/option.py:984 msgid "unknown type_ {0} for hostname" msgstr "" -#: tiramisu/option.py:982 +#: tiramisu/option.py:987 msgid "allow_ip must be a boolean" msgstr "" -#: tiramisu/option.py:1012 -msgid "invalid value for {0}, must have dot" +#: tiramisu/option.py:989 +msgid "allow_without_dot must be a boolean" msgstr "" -#: tiramisu/option.py:1015 -msgid "invalid domainname's length for {0} (max {1})" +#: tiramisu/option.py:1028 +msgid "invalid domainname, must have dot" msgstr "" -#: tiramisu/option.py:1018 -msgid "invalid domainname's length for {0} (min 2)" +#: tiramisu/option.py:1030 +msgid "invalid domainname's length (max 255)" msgstr "" -#: tiramisu/option.py:1022 +#: tiramisu/option.py:1032 +msgid "invalid domainname's length (min 2)" +msgstr "" + +#: tiramisu/option.py:1034 msgid "invalid domainname" msgstr "" -#: tiramisu/option.py:1049 +#: tiramisu/option.py:1047 +msgid "invalid email address, should contains one @" +msgstr "" + +#: tiramisu/option.py:1050 +msgid "invalid username in email address" +msgstr "" + +#: tiramisu/option.py:1063 +msgid "invalid url, should start with http:// or https://" +msgstr "" + +#: tiramisu/option.py:1082 +msgid "invalid url, port must be an between 0 and 65536" +msgstr "" + +#: tiramisu/option.py:1088 +msgid "invalid url, should ends with filename" +msgstr "" + +#: tiramisu/option.py:1099 +msgid "invalid filename" +msgstr "" + +#: tiramisu/option.py:1126 msgid "duplicate option name: {0}" msgstr "" -#: tiramisu/option.py:1067 +#: tiramisu/option.py:1144 msgid "unknown Option {0} in OptionDescription {1}" msgstr "" -#: tiramisu/option.py:1118 +#: tiramisu/option.py:1195 msgid "duplicate option: {0}" msgstr "" -#: tiramisu/option.py:1148 +#: tiramisu/option.py:1225 msgid "consistency with option {0} which is not in Config" msgstr "" -#: tiramisu/option.py:1156 +#: tiramisu/option.py:1233 msgid "no option for path {0}" msgstr "" -#: tiramisu/option.py:1162 +#: tiramisu/option.py:1239 msgid "no option {0} found" msgstr "" -#: tiramisu/option.py:1172 +#: tiramisu/option.py:1249 msgid "cannot change group_type if already set (old {0}, new {1})" msgstr "" -#: tiramisu/option.py:1185 +#: tiramisu/option.py:1261 msgid "master group {0} shall not have a subgroup" msgstr "" -#: tiramisu/option.py:1188 +#: tiramisu/option.py:1264 msgid "master group {0} shall not have a symlinkoption" msgstr "" -#: tiramisu/option.py:1191 +#: tiramisu/option.py:1267 msgid "not allowed option {0} in group {1}: this option is not a multi" msgstr "" -#: tiramisu/option.py:1202 +#: tiramisu/option.py:1277 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}" +#: tiramisu/option.py:1285 +msgid "callback of master's option shall not refered a slave's ones" msgstr "" -#: tiramisu/option.py:1214 +#: tiramisu/option.py:1293 msgid "group_type: {0} not allowed" msgstr "" -#: tiramisu/option.py:1306 +#: tiramisu/option.py:1385 msgid "malformed requirements type for option: {0}, must be a dict" msgstr "" -#: tiramisu/option.py:1323 +#: tiramisu/option.py:1402 msgid "malformed requirements for option: {0} require must have option, expected and action keys" msgstr "" -#: tiramisu/option.py:1328 +#: tiramisu/option.py:1407 msgid "malformed requirements for option: {0} inverse must be boolean" msgstr "" -#: tiramisu/option.py:1332 +#: tiramisu/option.py:1411 msgid "malformed requirements for option: {0} transitive must be boolean" msgstr "" -#: tiramisu/option.py:1336 +#: tiramisu/option.py:1415 msgid "malformed requirements for option: {0} same_action must be boolean" msgstr "" -#: tiramisu/option.py:1340 +#: tiramisu/option.py:1419 msgid "malformed requirements must be an option in option {0}" msgstr "" -#: tiramisu/option.py:1343 +#: tiramisu/option.py:1422 msgid "malformed requirements option {0} should not be a multi" msgstr "" -#: tiramisu/option.py:1349 +#: tiramisu/option.py:1428 msgid "malformed requirements second argument must be valid for option {0}: {1}" msgstr "" -#: tiramisu/option.py:1354 +#: tiramisu/option.py:1433 msgid "inconsistency in action types for option: {0} action: {1}" msgstr "" -#: tiramisu/option.py:1379 +#: tiramisu/option.py:1458 msgid "{0} should be a function" msgstr "" -#: tiramisu/option.py:1382 +#: tiramisu/option.py:1461 msgid "{0}_params should be a dict" msgstr "" -#: tiramisu/option.py:1385 +#: tiramisu/option.py:1464 msgid "{0}_params with key {1} should not have length different to 1" msgstr "" -#: tiramisu/option.py:1389 +#: tiramisu/option.py:1468 msgid "{0}_params should be tuple for key \"{1}\"" msgstr "" -#: tiramisu/option.py:1395 +#: tiramisu/option.py:1474 msgid "validator not support tuple" msgstr "" -#: tiramisu/option.py:1398 +#: tiramisu/option.py:1477 msgid "{0}_params should have an option not a {0} for first argument" msgstr "" -#: tiramisu/option.py:1402 +#: tiramisu/option.py:1481 msgid "{0}_params should have a boolean not a {0} for second argument" msgstr "" -#: tiramisu/setting.py:111 +#: tiramisu/setting.py:116 msgid "can't rebind {0}" msgstr "" -#: tiramisu/setting.py:116 +#: 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:483 msgid "cannot change the value for option {0} this option is frozen" msgstr "" -#: tiramisu/setting.py:441 +#: tiramisu/setting.py:489 msgid "trying to access to an option named: {0} with properties {1}" msgstr "" -#: tiramisu/setting.py:459 +#: tiramisu/setting.py:507 msgid "permissive must be a tuple" msgstr "" -#: tiramisu/setting.py:466 tiramisu/value.py:299 +#: tiramisu/setting.py:514 tiramisu/value.py:315 msgid "invalid generic owner {0}" msgstr "" -#: tiramisu/setting.py:553 +#: tiramisu/setting.py:602 msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'" msgstr "" -#: tiramisu/setting.py:565 +#: tiramisu/setting.py:613 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:322 msgid "no value for {0} cannot change owner to {1}" msgstr "" -#: tiramisu/value.py:414 +#: tiramisu/value.py:442 msgid "invalid len for the slave: {0} which has {1} as master" msgstr "" -#: tiramisu/value.py:438 +#: tiramisu/value.py:466 msgid "invalid len for the master: {0} which has {1} as slave with greater len" msgstr "" -#: tiramisu/value.py:468 +#: tiramisu/value.py:496 msgid "cannot append a value on a multi option {0} which is a slave" msgstr "" -#: tiramisu/value.py:505 +#: tiramisu/value.py:535 msgid "cannot sort multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:509 +#: tiramisu/value.py:539 msgid "cmp is not permitted in python v3 or greater" msgstr "" -#: tiramisu/value.py:518 +#: tiramisu/value.py:548 msgid "cannot reverse multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:526 +#: tiramisu/value.py:556 msgid "cannot insert multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:534 +#: tiramisu/value.py:564 msgid "cannot extend multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:562 +#: tiramisu/value.py:575 +msgid "invalid value {0} for option {1}: {2}" +msgstr "" + +#: tiramisu/value.py:593 msgid "cannot pop a value on a multi option {0} which is a slave" msgstr ""