diff --git a/Makefile b/Makefile index ca436a5..e9fabdb 100644 --- a/Makefile +++ b/Makefile @@ -24,13 +24,13 @@ define gettext P="pygettext.py" ; \ fi ; \ $$P -p translations/ -o $(PACKAGE).pot `find $(PACKAGE)/ -name "*.py"` -endef +endef # Build translation files define build_translation if [ -d ${1} ]; then \ for f in `find ${1} -name "*.po"`; do \ - msgfmt -o `dirname $$f`/`basename -s ".po" $$f`.mo $$f || true; \ + msgfmt -o `dirname $$f`/`basename $$f ".po"`.mo $$f || true; \ done; \ fi endef diff --git a/doc/api/tiramisu.storage.txt b/doc/api/tiramisu.storage.txt new file mode 100644 index 0000000..86cb062 --- /dev/null +++ b/doc/api/tiramisu.storage.txt @@ -0,0 +1,6 @@ +tiramisu.storage +================ + +.. automodule:: tiramisu.storage + :members: + :noindex: \ No newline at end of file diff --git a/doc/config.png b/doc/config.png new file mode 100644 index 0000000..a468275 Binary files /dev/null and b/doc/config.png differ diff --git a/doc/config.svg b/doc/config.svg new file mode 100644 index 0000000..3ff4bc9 --- /dev/null +++ b/doc/config.svg @@ -0,0 +1,257 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + Config + + + + + OptionDescription + + + + Option + + + + Option + + + + OptionDescription + + + + Option + + + + + + + + diff --git a/setup.py b/setup.py index 1b73eea..1e67a75 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup -from os.path import dirname, abspath, join, normpath, isdir, basename +from os.path import dirname, abspath, join, normpath, isdir from os import listdir @@ -9,15 +9,16 @@ def fetch_version(): """Get version from version.in""" return file('VERSION', 'r').readline().strip() + def return_storages(): "returns all the storage plugins that are living in tiramisu/storage" here = dirname(abspath(__file__)) storages_path = normpath(join(here, 'tiramisu', 'storage')) - dir_content = [ content for content in listdir(storages_path) \ - if not content =='__pycache__'] - storages = filter(isdir, [join(storages_path, content) \ + dir_content = [content for content in listdir(storages_path) + if not content == '__pycache__'] + storages = filter(isdir, [join(storages_path, content) for content in dir_content]) - storage_list = [basename(storage) for storage in storages] + storage_list = ['.'.join(storage.split('/')[-3:]) for storage in storages] return storage_list packages = ['tiramisu', 'tiramisu.storage'] @@ -58,6 +59,6 @@ producing flexible and fast options access. This version requires Python 2.6 or later. -""" +""", packages=packages ) diff --git a/test/test_config.py b/test/test_config.py index 17e863a..e091294 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -230,3 +230,9 @@ def test_duplicated_option(): root = OptionDescription('root', '', [d1, d2]) #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") + diff --git a/test/test_config_api.py b/test/test_config_api.py index ba268bc..ab4b484 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -116,6 +116,23 @@ def test_find_in_config(): #assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy') +def test_find_multi(): + b = BoolOption('bool', '', multi=True) + o = OptionDescription('od', '', [b]) + conf = Config(o) + raises(AttributeError, "conf.find(byvalue=True)") + raises(AttributeError, "conf.find_first(byvalue=True)") + conf.bool.append(False) + raises(AttributeError, "conf.find(byvalue=True)") + raises(AttributeError, "conf.find_first(byvalue=True)") + conf.bool.append(False) + raises(AttributeError, "conf.find(byvalue=True)") + raises(AttributeError, "conf.find_first(byvalue=True)") + conf.bool.append(True) + assert conf.find(byvalue=True) == [b] + assert conf.find_first(byvalue=True) == b + + def test_does_not_find_in_config(): descr = make_description() conf = Config(descr) diff --git a/test/test_config_ip.py b/test/test_config_ip.py index e889c92..b7d3010 100644 --- a/test/test_config_ip.py +++ b/test/test_config_ip.py @@ -7,7 +7,7 @@ from tiramisu.option import IPOption, NetworkOption, NetmaskOption, \ def test_ip(): a = IPOption('a', '') - b = IPOption('b', '', only_private=True) + b = IPOption('b', '', private_only=True) od = OptionDescription('od', '', [a, b]) c = Config(od) c.a = '192.168.1.1' @@ -29,6 +29,15 @@ def test_ip_default(): c.a == '88.88.88.88' +def test_ip_reserved(): + a = IPOption('a', '') + b = IPOption('b', '', allow_reserved=True) + od = OptionDescription('od', '', [a, b]) + c = Config(od) + raises(ValueError, "c.a = '226.94.1.1'") + c.b = '226.94.1.1' + + def test_network(): a = NetworkOption('a', '') od = OptionDescription('od', '', [a]) diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 0266855..117de9d 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -4,19 +4,33 @@ from py.test import raises from tiramisu.setting import groups from tiramisu.config import Config from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ - StrOption, OptionDescription -from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError + StrOption, OptionDescription, SymLinkOption +from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError def return_val(): return 'val' -def return_list(): +def return_concat(*args): + return '.'.join(list(args)) + + +def return_list(value=None): return ['val', 'val'] -def return_value(value): +def return_list2(*args): + return list(args) + + +def return_value(value=None): + return value + + +def return_value2(*args, **kwargs): + value = list(args) + value.extend(kwargs.values()) return value @@ -298,18 +312,73 @@ def test_callback(): def test_callback_value(): val1 = StrOption('val1', "", 'val') - val2 = StrOption('val2', "", callback=return_value, callback_params={'': (('val1', False),)}) - maconfig = OptionDescription('rootconfig', '', [val1, val2]) + val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)}) + val3 = StrOption('val3', "", callback=return_value, callback_params={'': ('yes',)}) + val4 = StrOption('val4', "", callback=return_value, callback_params={'value': ((val1, False),)}) + val5 = StrOption('val5', "", callback=return_value, callback_params={'value': ('yes',)}) + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5]) cfg = Config(maconfig) cfg.read_write() assert cfg.val1 == 'val' assert cfg.val2 == 'val' + assert cfg.val4 == 'val' cfg.val1 = 'new-val' assert cfg.val1 == 'new-val' assert cfg.val2 == 'new-val' + assert cfg.val4 == 'new-val' del(cfg.val1) assert cfg.val1 == 'val' assert cfg.val2 == 'val' + assert cfg.val3 == 'yes' + assert cfg.val4 == 'val' + assert cfg.val5 == 'yes' + + +def test_callback_value_tuple(): + val1 = StrOption('val1', "", 'val1') + val2 = StrOption('val2', "", 'val2') + val3 = StrOption('val3', "", callback=return_concat, callback_params={'': ((val1, False), (val2, False))}) + val4 = StrOption('val4', "", callback=return_concat, callback_params={'': ('yes', 'no')}) + raises(ValueError, "StrOption('val4', '', callback=return_concat, callback_params={'value': ('yes', 'no')})") + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1 == 'val1' + assert cfg.val2 == 'val2' + assert cfg.val3 == 'val1.val2' + assert cfg.val4 == 'yes.no' + cfg.val1 = 'new-val' + assert cfg.val3 == 'new-val.val2' + del(cfg.val1) + assert cfg.val3 == 'val1.val2' + + +def test_callback_value_force_permissive(): + val1 = StrOption('val1', "", 'val', properties=('disabled',)) + val2 = StrOption('val2', "", callback=return_value, callback_params={'': ((val1, False),)}) + val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val1, True),)}) + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3]) + cfg = Config(maconfig) + cfg.read_only() + raises(ConfigError, "cfg.val2") + assert cfg.val3 is None + + +def test_callback_symlink(): + val1 = StrOption('val1', "", 'val') + val2 = SymLinkOption('val2', val1) + val3 = StrOption('val3', "", callback=return_value, callback_params={'': ((val2, False),)}) + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1 == 'val' + assert cfg.val3 == 'val' + cfg.val1 = 'new-val' + assert cfg.val1 == 'new-val' + assert cfg.val3 == 'new-val' + del(cfg.val1) + assert cfg.val1 == 'val' + assert cfg.val3 == 'val' def test_callback_list(): @@ -336,21 +405,28 @@ def test_callback_multi(): def test_callback_multi_value(): val1 = StrOption('val1', "", ['val'], multi=True) - val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1', False),)}) - maconfig = OptionDescription('rootconfig', '', [val1, val2]) + val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) + val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)}) + val4 = StrOption('val4', "", multi=True, callback=return_list2, callback_params={'': ((val1, False), 'yes')}) + maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4]) cfg = Config(maconfig) cfg.read_write() assert cfg.val1 == ['val'] assert cfg.val2 == ['val'] + assert cfg.val4 == ['val', 'yes'] cfg.val1 = ['new-val'] assert cfg.val1 == ['new-val'] assert cfg.val2 == ['new-val'] + assert cfg.val4 == ['new-val', 'yes'] 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'] del(cfg.val1) assert cfg.val1 == ['val'] assert cfg.val2 == ['val'] + assert cfg.val3 == ['yes'] + assert cfg.val4 == ['val', 'yes'] def test_callback_multi_list(): @@ -455,41 +531,67 @@ def test_callback_master_and_slaves_slave_list(): def test_callback_master_and_slaves_value(): val1 = StrOption('val1', "", multi=True) - val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': (('val1.val1', False),)}) - interface1 = OptionDescription('val1', '', [val1, val2]) + val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) + 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]) interface1.impl_set_group_type(groups.master) - maconfig = OptionDescription('rootconfig', '', [interface1]) + maconfig = OptionDescription('rootconfig', '', [interface1, val4]) cfg = Config(maconfig) cfg.read_write() assert cfg.val1.val1 == [] assert cfg.val1.val2 == [] + assert cfg.val1.val3 == [] + assert cfg.val1.val5 == [] # cfg.val1.val1 = ['val1'] assert cfg.val1.val1 == ['val1'] assert cfg.val1.val2 == ['val1'] + assert cfg.val1.val3 == ['yes'] + assert cfg.val1.val5 == ['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'] # 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] # 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'] # cfg.val1.val2 = ['val2', 'val2'] + cfg.val1.val3 = ['val2', 'val2'] + cfg.val1.val5 = ['val2', 'val2'] assert cfg.val1.val2 == ['val2', 'val2'] + assert cfg.val1.val3 == ['val2', 'val2'] + assert cfg.val1.val5 == ['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] + cfg.cfgimpl_get_settings().remove('cache') + cfg.val4 = ['val10', 'val11', 'val12'] + #if value is already set, not updated ! + cfg.val1.val1.pop(2) + cfg.val1.val1.append('val3') + cfg.val1.val1 = ['val1', 'val2', 'val3'] + assert cfg.val1.val5 == ['val2', 'val2', 'val12'] def test_callback_hidden(): opt1 = BoolOption('opt1', '') - opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': (('od1.opt1', False),)}) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}) od1 = OptionDescription('od1', '', [opt1], properties=('hidden',)) od2 = OptionDescription('od2', '', [opt2]) maconfig = OptionDescription('rootconfig', '', [od1, od2]) @@ -498,3 +600,94 @@ def test_callback_hidden(): cfg.read_write() raises(PropertiesOptionError, 'cfg.od1.opt1') cfg.od2.opt2 + + +def test_callback_two_disabled(): + opt1 = BoolOption('opt1', '', properties=('disabled',)) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',)) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_write() + raises(PropertiesOptionError, 'cfg.od2.opt2') + + +def test_callback_calculating_disabled(): + opt1 = BoolOption('opt1', '', properties=('disabled',)) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_write() + raises(ConfigError, 'cfg.od2.opt2') + + +def test_callback_calculating_mandatory(): + opt1 = BoolOption('opt1', '', properties=('disabled',)) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',)) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_only() + raises(ConfigError, 'cfg.od2.opt2') + + +def test_callback_two_disabled_multi(): + opt1 = BoolOption('opt1', '', properties=('disabled',)) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_write() + raises(PropertiesOptionError, 'cfg.od2.opt2') + + +def test_callback_multi_list_params(): + val1 = StrOption('val1', "", multi=True, default=['val1', 'val2']) + val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'': ((val1, False),)}) + oval2 = OptionDescription('val2', '', [val2]) + maconfig = OptionDescription('rootconfig', '', [val1, oval2]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val2.val2 == ['val', 'val', 'val', 'val'] + + +def test_callback_multi_list_params_key(): + val1 = StrOption('val1', "", multi=True, default=['val1', 'val2']) + val2 = StrOption('val2', "", multi=True, callback=return_list, callback_params={'value': ((val1, False),)}) + oval2 = OptionDescription('val2', '', [val2]) + 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") diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index 5cf53cd..d5226db 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -4,7 +4,8 @@ from py.test import raises from tiramisu.setting import owners, groups from tiramisu.config import Config from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ - SymLinkOption, OptionDescription + BroadcastOption, SymLinkOption, OptionDescription +from tiramisu.error import ConfigError def test_consistency_not_equal(): @@ -22,6 +23,60 @@ def test_consistency_not_equal(): c.b = 2 +def test_consistency_not_equal_many_opts(): + a = IntOption('a', '') + b = IntOption('b', '') + c = IntOption('c', '') + d = IntOption('d', '') + e = IntOption('e', '') + f = IntOption('f', '') + od = OptionDescription('od', '', [a, b, c, d, e, f]) + a.impl_add_consistency('not_equal', b, c, d, e, f) + c = Config(od) + assert c.a is None + assert c.b is None + # + c.a = 1 + del(c.a) + # + c.a = 1 + raises(ValueError, "c.b = 1") + # + c.b = 2 + raises(ValueError, "c.f = 2") + raises(ValueError, "c.f = 1") + # + c.d = 3 + raises(ValueError, "c.f = 3") + raises(ValueError, "c.a = 3") + raises(ValueError, "c.c = 3") + raises(ValueError, "c.e = 3") + + +def test_consistency_not_in_config(): + a = IntOption('a', '') + b = IntOption('b', '') + a.impl_add_consistency('not_equal', b) + od1 = OptionDescription('od1', '', [a]) + od2 = OptionDescription('od2', '', [b]) + od = OptionDescription('root', '', [od1]) + raises(ConfigError, "Config(od)") + od = OptionDescription('root', '', [od1, od2]) + Config(od) + #with subconfig + raises(ConfigError, "Config(od.od1)") + + +def test_consistency_afer_config(): + a = IntOption('a', '') + b = IntOption('b', '') + od1 = OptionDescription('od1', '', [a]) + od2 = OptionDescription('od2', '', [b]) + od = OptionDescription('root', '', [od1, od2]) + Config(od) + raises(AttributeError, "a.impl_add_consistency('not_equal', b)") + + def test_consistency_not_equal_symlink(): a = IntOption('a', '') b = IntOption('b', '') @@ -29,7 +84,7 @@ def test_consistency_not_equal_symlink(): od = OptionDescription('od', '', [a, b, c]) a.impl_add_consistency('not_equal', b) c = Config(od) - assert set(od._consistencies.keys()) == set([a, b]) + assert set(od._cache_consistencies.keys()) == set([a, b]) def test_consistency_not_equal_multi(): @@ -53,6 +108,14 @@ def test_consistency_default(): raises(ValueError, "a.impl_add_consistency('not_equal', b)") +def test_consistency_default_multi(): + a = IntOption('a', '', [2, 1], multi=True) + b = IntOption('b', '', [1, 1], multi=True) + c = IntOption('c', '', [1, 2], multi=True) + raises(ValueError, "a.impl_add_consistency('not_equal', b)") + a.impl_add_consistency('not_equal', c) + + def test_consistency_default_diff(): a = IntOption('a', '', 3) b = IntOption('b', '', 1) @@ -99,7 +162,7 @@ def test_consistency_ip_netmask_error_multi(): a = IPOption('a', '', multi=True) b = NetmaskOption('b', '') od = OptionDescription('od', '', [a, b]) - raises(ValueError, "b.impl_add_consistency('ip_netmask', a)") + raises(ConfigError, "b.impl_add_consistency('ip_netmask', a)") def test_consistency_ip_netmask_multi(): @@ -159,3 +222,53 @@ def test_consistency_network_netmask_multi_master(): c.a = ['192.168.1.0'] c.b = ['255.255.255.0'] raises(ValueError, "c.a = ['192.168.1.1']") + + +def test_consistency_broadcast(): + a = NetworkOption('a', '', multi=True) + b = NetmaskOption('b', '', multi=True) + c = BroadcastOption('c', '', multi=True) + od = OptionDescription('a', '', [a, b, c]) + od.impl_set_group_type(groups.master) + b.impl_add_consistency('network_netmask', a) + c.impl_add_consistency('broadcast', a, b) + c = Config(od) + #first, test network_netmask + c.a = ['192.168.1.128'] + raises(ValueError, "c.b = ['255.255.255.0']") + # + c.a = ['192.168.1.0'] + c.b = ['255.255.255.0'] + c.c = ['192.168.1.255'] + raises(ValueError, "c.a = ['192.168.1.1']") + # + c.a = ['192.168.1.0', '192.168.2.128'] + c.b = ['255.255.255.0', '255.255.255.128'] + c.c = ['192.168.1.255', '192.168.2.255'] + raises(ValueError, "c.c[1] = '192.168.2.128'") + c.c[1] = '192.168.2.255' + + +def test_consistency_broadcast_default(): + a = NetworkOption('a', '', '192.168.1.0') + b = NetmaskOption('b', '', '255.255.255.128') + c = BroadcastOption('c', '', '192.168.2.127') + d = BroadcastOption('d', '', '192.168.1.127') + od = OptionDescription('a', '', [a, b, c]) + raises(ValueError, "c.impl_add_consistency('broadcast', a, b)") + od2 = OptionDescription('a', '', [a, b, d]) + d.impl_add_consistency('broadcast', a, b) + + +def test_consistency_not_all(): + #_cache_consistencies is not None by not options has consistencies + a = NetworkOption('a', '', multi=True) + b = NetmaskOption('b', '', multi=True) + c = BroadcastOption('c', '', multi=True) + od = OptionDescription('a', '', [a, b, c]) + od.impl_set_group_type(groups.master) + b.impl_add_consistency('network_netmask', a) + c = Config(od) + c.a = ['192.168.1.0'] + c.b = ['255.255.255.0'] + c.c = ['192.168.1.255'] diff --git a/test/test_option_setting.py b/test/test_option_setting.py index 2dc76ab..ed5f7d7 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -327,23 +327,23 @@ def test_reset_properties(): cfg = Config(descr) setting = cfg.cfgimpl_get_settings() option = cfg.cfgimpl_get_description().gc.dummy - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} setting.append('frozen') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'cache', 'validator'))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'cache', 'validator'))} setting.reset() - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} setting[option].append('test') - assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))} setting.reset() - assert setting._p_.get_properties(cfg) == {'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {'gc.dummy': set(('test',))} setting.append('frozen') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} setting.reset(option) - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator', 'cache'))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache'))} setting[option].append('test') - assert setting._p_.get_properties(cfg) == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} + assert setting._p_.get_modified_properties() == {None: set(('frozen', 'expire', 'validator', 'cache')), 'gc.dummy': set(('test',))} setting.reset(all_properties=True) - assert setting._p_.get_properties(cfg) == {} + assert setting._p_.get_modified_properties() == {} raises(ValueError, 'setting.reset(all_properties=True, opt=option)') a = descr.wantref setting[a].append('test') diff --git a/test/test_option_validator.py b/test/test_option_validator.py new file mode 100644 index 0000000..001f9f7 --- /dev/null +++ b/test/test_option_validator.py @@ -0,0 +1,155 @@ +import autopath +import warnings +from py.test import raises + +from tiramisu.config import Config +from tiramisu.option import StrOption, OptionDescription +from tiramisu.setting import groups +from tiramisu.error import ValueWarning + + +def return_true(value, param=None): + if value == 'val' and param in [None, 'yes']: + return True + + +def return_false(value, param=None): + if value == 'val' and param in [None, 'yes']: + raise ValueError('error') + + +def return_val(value, param=None): + return 'val' + + +def return_if_val(value): + if value != 'val': + raise ValueError('error') + + +def test_validator(): + opt1 = StrOption('opt1', '', validator=return_true, default='val') + raises(ValueError, "StrOption('opt2', '', validator=return_false, default='val')") + opt2 = StrOption('opt2', '', validator=return_false) + root = OptionDescription('root', '', [opt1, opt2]) + cfg = Config(root) + assert cfg.opt1 == 'val' + raises(ValueError, "cfg.opt2 = 'val'") + + +def test_validator_params(): + opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ('yes',)}, default='val') + raises(ValueError, "StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}, default='val')") + opt2 = StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}) + root = OptionDescription('root', '', [opt1, opt2]) + cfg = Config(root) + assert cfg.opt1 == 'val' + raises(ValueError, "cfg.opt2 = 'val'") + + +def test_validator_params_key(): + opt1 = StrOption('opt1', '', validator=return_true, validator_params={'param': ('yes',)}, default='val') + raises(TypeError, "StrOption('opt2', '', validator=return_true, validator_params={'param_unknown': ('yes',)}, default='val')") + root = OptionDescription('root', '', [opt1]) + cfg = Config(root) + assert cfg.opt1 == 'val' + + +def test_validator_params_option(): + opt0 = StrOption('opt0', '', default='val') + raises(ValueError, "opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ((opt0, False),)}, default='val')") + + +def test_validator_multi(): + opt1 = StrOption('opt1', '', validator=return_if_val, multi=True) + root = OptionDescription('root', '', [opt1]) + cfg = Config(root) + assert cfg.opt1 == [] + cfg.opt1.append('val') + assert cfg.opt1 == ['val'] + raises(ValueError, "cfg.opt1.append('val1')") + raises(ValueError, "cfg.opt1 = ['val', 'val1']") + + +def test_validator_warning(): + opt1 = StrOption('opt1', '', validator=return_true, default='val', warnings_only=True) + opt2 = StrOption('opt2', '', validator=return_false, warnings_only=True) + opt3 = StrOption('opt3', '', validator=return_if_val, multi=True, warnings_only=True) + root = OptionDescription('root', '', [opt1, opt2, opt3]) + cfg = Config(root) + assert cfg.opt1 == 'val' + warnings.simplefilter("always", ValueWarning) + with warnings.catch_warnings(record=True) as w: + cfg.opt1 = 'val' + assert w == [] + # + with warnings.catch_warnings(record=True) as w: + 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' + # + with warnings.catch_warnings(record=True) as w: + cfg.opt3.append('val') + assert w == [] + # + with warnings.catch_warnings(record=True) as w: + 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' + raises(ValueError, "cfg.opt2 = 1") + # + with warnings.catch_warnings(record=True) as w: + cfg.opt2 = 'val' + 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 w[1].message.opt == opt3 + assert str(w[1].message) == 'invalid value val1 for option opt3: error' + + +def test_validator_warning_master_slave(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip reseau autorise", multi=True, validator=return_false, warnings_only=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-reseau", multi=True, validator=return_if_val, warnings_only=True) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + assert interface1.impl_get_group_type() == groups.master + root = OptionDescription('root', '', [interface1]) + cfg = Config(root) + warnings.simplefilter("always", ValueWarning) + with warnings.catch_warnings(record=True) as w: + cfg.ip_admin_eth0.ip_admin_eth0.append(None) + assert w == [] + # + with warnings.catch_warnings(record=True) as w: + 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' + # + 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' + # + 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' + # + 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' + # + 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' diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 7b9dffe..7ecd860 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -64,9 +64,9 @@ def test_make_dict_filter(): config = Config(descr) config.read_write() subresult = {'numero_etab': None, 'nombre_interfaces': 1, - 'serveur_ntp': [], 'mode_conteneur_actif': False, - 'time_zone': 'Paris', 'nom_machine': 'eoleng', - 'activer_proxy_client': False} + 'serveur_ntp': [], 'mode_conteneur_actif': False, + 'time_zone': 'Paris', 'nom_machine': 'eoleng', + 'activer_proxy_client': False} result = {} for key, value in subresult.items(): result['general.' + key] = value @@ -114,7 +114,6 @@ def test_iter_not_group(): raises(TypeError, "list(config.iter_groups(group_type='family'))") - def test_groups_with_master(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) @@ -252,6 +251,22 @@ def test_values_with_master_and_slaves_master(): assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] +def test_values_with_master_and_slaves_master_error(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"] + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']") + cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0'] + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']") + + def test_values_with_master_owner(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) diff --git a/test/test_state.py b/test/test_state.py index 03ab670..ef46ce2 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -1,8 +1,16 @@ from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \ OptionDescription +from tiramisu.config import Config +from tiramisu.setting import owners +from tiramisu.storage import delete_session +from tiramisu.error import ConfigError from pickle import dumps, loads +def return_value(value=None): + return value + + def _get_slots(opt): slots = set() for subclass in opt.__class__.__mro__: @@ -32,7 +40,7 @@ def _diff_opt(opt1, opt2): if diff2 != set(): raise Exception('more attribute in opt2 {0}'.format(list(diff2))) for attr in attr1: - if attr in ['_cache_paths']: + if attr in ['_cache_paths', '_cache_consistencies']: continue err1 = False err2 = False @@ -64,7 +72,20 @@ def _diff_opt(opt1, opt2): if isinstance(val1, list): for index, consistency in enumerate(val1): assert consistency[0] == val2[index][0] - assert consistency[1]._name == val2[index][1]._name + for idx, opt in enumerate(consistency[1]): + assert opt._name == val2[index][1][idx]._name + elif attr == '_callback': + assert val1[0] == val2[0] + if val1[1] is not None: + for key, values in val1[1].items(): + for idx, value in enumerate(values): + if isinstance(value, tuple): + assert val1[1][key][idx][0]._name == val2[1][key][idx][0]._name + assert val1[1][key][idx][1] == val2[1][key][idx][1] + else: + assert val1[1][key][idx] == val2[1][key][idx] + else: + assert val1[1] == val2[1] else: assert val1 == val2 @@ -104,6 +125,23 @@ def test_diff_opt_cache(): _diff_opt(o1.o.s, q.o.s) +def test_diff_opt_callback(): + b = BoolOption('b', '', callback=return_value) + b2 = BoolOption('b2', '', callback=return_value, callback_params={'': ('yes',)}) + b3 = BoolOption('b3', '', callback=return_value, callback_params={'': ('yes', (b, False)), 'value': ('no',)}) + o = OptionDescription('o', '', [b, b2, b3]) + o1 = OptionDescription('o1', '', [o]) + o1.impl_build_cache() + + a = dumps(o1) + q = loads(a) + _diff_opt(o1, q) + _diff_opt(o1.o, q.o) + _diff_opt(o1.o.b, q.o.b) + _diff_opt(o1.o.b2, q.o.b2) + _diff_opt(o1.o.b3, q.o.b3) + + def test_no_state_attr(): # all _state_xxx attributes should be deleted b = BoolOption('b', '') @@ -119,3 +157,95 @@ def test_no_state_attr(): _no_state(q.o.b) _no_state(q.o.u) _no_state(q.o.s) + + +def test_state_config(): + val1 = BoolOption('val1', "") + maconfig = OptionDescription('rootconfig', '', [val1]) + try: + cfg = Config(maconfig, persistent=True, session_id='29090931') + except ValueError: + cfg = Config(maconfig, session_id='29090931') + cfg._impl_test = True + a = dumps(cfg) + q = loads(a) + _diff_opt(maconfig, q.cfgimpl_get_description()) + assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values() + assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties() + assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives() + try: + delete_session('29090931') + except ConfigError: + pass + + +def test_state_properties(): + val1 = BoolOption('val1', "") + maconfig = OptionDescription('rootconfig', '', [val1]) + try: + cfg = Config(maconfig, persistent=True, session_id='29090932') + except ValueError: + cfg = Config(maconfig, session_id='29090932') + cfg._impl_test = True + cfg.read_write() + cfg.cfgimpl_get_settings()[val1].append('test') + a = dumps(cfg) + q = loads(a) + _diff_opt(maconfig, q.cfgimpl_get_description()) + assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values() + assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties() + assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives() + try: + delete_session('29090931') + except ConfigError: + pass + + +def test_state_values(): + val1 = BoolOption('val1', "") + maconfig = OptionDescription('rootconfig', '', [val1]) + try: + cfg = Config(maconfig, persistent=True, session_id='29090933') + except ValueError: + cfg = Config(maconfig, session_id='29090933') + cfg._impl_test = True + cfg.val1 = True + a = dumps(cfg) + q = loads(a) + _diff_opt(maconfig, q.cfgimpl_get_description()) + assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values() + assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties() + assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives() + q.val1 = False + #assert cfg.val1 is True + assert q.val1 is False + try: + delete_session('29090931') + except ConfigError: + pass + + +def test_state_values_owner(): + val1 = BoolOption('val1', "") + maconfig = OptionDescription('rootconfig', '', [val1]) + try: + cfg = Config(maconfig, persistent=True, session_id='29090934') + except ValueError: + cfg = Config(maconfig, session_id='29090934') + cfg._impl_test = True + owners.addowner('newowner') + cfg.cfgimpl_get_settings().setowner(owners.newowner) + cfg.val1 = True + a = dumps(cfg) + q = loads(a) + _diff_opt(maconfig, q.cfgimpl_get_description()) + assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values() + assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties() + assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives() + q.val1 = False + nval1 = q.cfgimpl_get_description().val1 + assert q.getowner(nval1) == owners.newowner + try: + delete_session('29090931') + except ConfigError: + pass diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 2cf41ca..efb7c0e 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -23,11 +23,9 @@ from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.i18n import _ # ____________________________________________________________ -def carry_out_calculation(name, - config, - callback, - callback_params, - index=None): + +def carry_out_calculation(name, 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`) @@ -40,73 +38,161 @@ def carry_out_calculation(name, :type callback_params: dict :param index: if an option is multi, only calculates the nth value :type index: int - """ - #callback, callback_params = option.getcallback() - #if callback_params is None: - # callback_params = {} - tcparams = {} - one_is_multi = False - len_multi = 0 + :param max_len: max length for a multi + :type max_len: int - for key, values in callback_params.items(): - for value in values: - if type(value) == tuple: - path, check_disabled = value - if config is None: - if check_disabled: - continue - raise ConfigError(_('no config specified but needed')) + The callback_params is a dict. Key is used to build args (if key is '') + and kwargs (otherwise). Values are tuple of: + - values + - tuple with option and boolean's force_permissive (True when don't raise + if PropertiesOptionError) + Values could have multiple values only when key is ''. + + * if no callback_params: + => calculate() + + * if callback_params={'': ('yes',)} + => calculate('yes') + + * if callback_params={'value': ('yes',)} + => calculate(value='yes') + + * if callback_params={'': ('yes', 'no')} + => calculate('yes', 'no') + + * if callback_params={'value': ('yes', 'no')} + => ValueError() + + * if callback_params={'': ((opt1, False),)} + + - a simple option: + opt1 == 11 + => calculate(11) + + - a multi option: + opt1 == [1, 2, 3] + => calculate(1) + => calculate(2) + => calculate(3) + + * if callback_params={'value': ((opt1, False),)} + + - a simple option: + opt1 == 11 + => calculate(value=11) + + - a multi option: + opt1 == [1, 2, 3] + => calculate(value=1) + => calculate(value=2) + => calculate(value=3) + + * if callback_params={'': ((opt1, False), (opt2, False))} + + - a multi option with a simple option + opt1 == [1, 2, 3] + opt2 == 11 + => calculate(1, 11) + => calculate(2, 11) + => calculate(3, 11) + + - 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) + + - a multi option with an other multi option but with different length + opt1 == [1, 2, 3] + opt2 == [11, 12] + => ConfigError() + + - a multi option without value with a simple option + opt1 == [] + opt2 == 11 + => [] + + * if callback_params={'value': ((opt1, False), (opt2, False))} + => ConfigError() + + If index is not None, return a value, otherwise return: + + * a list if one parameters have multi option + * a value otherwise + + If calculate return list, this list is extend to return value. + """ + tcparams = {} + # if callback_params has a callback, launch several time calculate() + one_is_multi = False + # multi's option should have same value for all option + len_multi = None + + for key, callbacks in callback_params.items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + # callbk is something link (opt, True|False) + option, force_permissive = callbk + path = config.cfgimpl_get_description().impl_get_path_by_opt( + option) + # get value try: - opt_value = config._getattr(path, force_permissive=True) - opt = config.unwrap_from_path(path, force_permissive=True) + value = config._getattr(path, force_permissive=True) except PropertiesOptionError as err: - if check_disabled: + if force_permissive: continue raise ConfigError(_('unable to carry out a calculation, ' 'option {0} has properties: {1} for: ' - '{2}').format(path, err.proptype, + '{2}').format(option._name, + err.proptype, name)) - is_multi = opt.impl_is_multi() + is_multi = option.impl_is_multi() if is_multi: - if opt_value is not None: - len_value = len(opt_value) - if len_multi != 0 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_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 one_is_multi = True - tcparams.setdefault(key, []).append((opt_value, is_multi)) + tcparams.setdefault(key, []).append((value, is_multi)) else: - tcparams.setdefault(key, []).append((value, False)) + # callbk is a value and not a multi + tcparams.setdefault(key, []).append((callbk, False)) + # if one value is a multi, launch several time calculate + # if index is set, return a value + # if no index, return a list if one_is_multi: ret = [] if index: - range_ = [index] + if index < len_multi: + range_ = [index] + else: + range_ = [] + ret = None else: - range_ = range(len_multi) + if max_len and max_len < len_multi: + range_ = range(max_len) + else: + range_ = range(len_multi) for incr in range_: - tcp = {} - params = [] + args = [] + kwargs = {} for key, couples in tcparams.items(): for couple in couples: value, ismulti = couple - if ismulti and value is not None: - if key == '': - params.append(value[incr]) - else: - if len(value) > incr: - tcp[key] = value[incr] - else: - tcp[key] = '' + if ismulti: + val = value[incr] else: - if key == '': - params.append(value) - else: - tcp[key] = value - calc = calculate(name, callback, params, tcp) + val = value + if key == '': + args.append(val) + else: + kwargs[key] = val + calc = calculate(callback, args, kwargs) if index: ret = calc else: @@ -114,28 +200,28 @@ def carry_out_calculation(name, ret.extend(calc) else: ret.append(calc) - return ret else: - tcp = {} - params = [] + # no value is multi + # return a single value + args = [] + kwargs = {} for key, couples in tcparams.items(): for couple in couples: + # couple[1] (ismulti) is always False if key == '': - value = couple[0] - params.append(value) + args.append(couple[0]) else: - tcp[key] = couple[0] - return calculate(name, callback, params, tcp) + kwargs[key] = couple[0] + return calculate(callback, args, kwargs) -def calculate(name, callback, params, tcparams): - # FIXME we don't need the option's name down there. +def calculate(callback, args, kwargs): """wrapper that launches the 'callback' - :param callback: callback name - :param params: in the callback's arity, the unnamed parameters - :param tcparams: in the callback's arity, the named parameters + :param callback: callback function + :param args: in the callback's arity, the unnamed parameters + :param kwargs: in the callback's arity, the named parameters """ - return callback(*params, **tcparams) + return callback(*args, **kwargs) diff --git a/tiramisu/config.py b/tiramisu/config.py index 591eb68..79527e8 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -"options handler global entry point" # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify @@ -20,17 +19,23 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +"options handler global entry point" import weakref from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.option import OptionDescription, Option, SymLinkOption from tiramisu.setting import groups, Settings, default_encoding -from tiramisu.storage import get_storages -from tiramisu.value import Values +from tiramisu.storage import get_storages, get_storage, set_storage, \ + _impl_getstate_setting +from tiramisu.value import Values, Multi from tiramisu.i18n import _ class SubConfig(object): - "sub configuration management entry" + """Sub configuration management entry. + Tree if OptionDescription's responsability. SubConfig are generated + on-demand. A Config is also a SubConfig. + Root Config is call context below + """ __slots__ = ('_impl_context', '_impl_descr', '_impl_path') def __init__(self, descr, context, subpath=None): @@ -55,6 +60,7 @@ class SubConfig(object): def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')): + "remove cache (in context)" self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only) def cfgimpl_get_home_by_path(self, path, force_permissive=False, @@ -179,7 +185,9 @@ class SubConfig(object): homeconfig, name = self.cfgimpl_get_home_by_path(name) return homeconfig.__setattr__(name, value) child = getattr(self.cfgimpl_get_description(), name) - if not isinstance(child, SymLinkOption): + if isinstance(child, OptionDescription): + raise TypeError(_("can't assign to an OptionDescription")) + elif not isinstance(child, SymLinkOption): if self._impl_path is None: path = name else: @@ -293,12 +301,13 @@ class SubConfig(object): return True try: value = getattr(self, path) - if value == byvalue: - return True + if isinstance(value, Multi): + return byvalue in value + else: + return value == byvalue except PropertiesOptionError: # a property is a restriction # upon the access of the value - pass - return False + return False def _filter_by_type(): if bytype is None: @@ -323,15 +332,15 @@ class SubConfig(object): continue if not _filter_by_value(): continue + if not _filter_by_type(): + continue #remove option with propertyerror, ... - if check_properties: + if byvalue is None and check_properties: try: value = getattr(self, path) except PropertiesOptionError: # a property restricts the access of the value continue - if not _filter_by_type(): - continue if type_ == 'value': retval = value elif type_ == 'path': @@ -521,7 +530,7 @@ class CommonConfig(SubConfig): # ____________________________________________________________ class Config(CommonConfig): "main configuration management entry" - __slots__ = ('__weakref__', ) + __slots__ = ('__weakref__', '_impl_test') def __init__(self, descr, session_id=None, persistent=False): """ Configuration option management master class @@ -542,6 +551,43 @@ class Config(CommonConfig): super(Config, self).__init__(descr, weakref.ref(self)) self._impl_build_all_paths() self._impl_meta = None + #undocumented option used only in test script + self._impl_test = False + + def __getstate__(self): + if self._impl_meta is not None: + raise ConfigError('cannot serialize Config with meta') + slots = set() + for subclass in self.__class__.__mro__: + if subclass is not object: + slots.update(subclass.__slots__) + slots -= frozenset(['_impl_context', '__weakref__']) + state = {} + for slot in slots: + try: + state[slot] = getattr(self, slot) + except AttributeError: + pass + storage = self._impl_values._p_._storage + if not storage.serializable: + raise ConfigError('this storage is not serialisable, could be a ' + 'none persistent storage') + state['_storage'] = {'session_id': storage.session_id, + 'persistent': storage.persistent} + state['_impl_setting'] = _impl_getstate_setting() + return state + + def __setstate__(self, state): + for key, value in state.items(): + if key not in ['_storage', '_impl_setting']: + setattr(self, key, value) + set_storage(**state['_impl_setting']) + self._impl_context = weakref.ref(self) + self._impl_settings.context = weakref.ref(self) + self._impl_values.context = weakref.ref(self) + storage = get_storage(test=self._impl_test, **state['_storage']) + self._impl_values._impl_setstate(storage) + self._impl_settings._impl_setstate(storage) def cfgimpl_reset_cache(self, only_expired=False, diff --git a/tiramisu/error.py b/tiramisu/error.py index 6c92be3..5694e4d 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -61,3 +61,35 @@ class SlaveError(Exception): class ConstError(TypeError): "no uniq value in _NameSpace" pass + + +#Warning +class ValueWarning(UserWarning): + """Option could warn user and not raise ValueError. + + Example: + + >>> import warnings + >>> from tiramisu.error import ValueWarning + >>> from tiramisu.option import StrOption, OptionDescription + >>> from tiramisu.config import Config + >>> warnings.simplefilter("always", ValueWarning) + >>> def a(val): + ... raise ValueError('pouet') + ... + >>> s=StrOption('s', '', validator=a, warnings_only=True) + >>> o=OptionDescription('o', '', [s]) + >>> c=Config(o) + >>> c.s = 'val' + StrOption:0: ValueWarning: invalid value val for option s: pouet + >>> with warnings.catch_warnings(record=True) as w: + ... c.s = 'val' + ... + >>> w[0].message.opt == s + True + >>> print str(w[0].message) + invalid value val for option s: pouet + """ + def __init__(self, msg, opt): + self.opt = opt + super(ValueWarning, self).__init__(msg) diff --git a/tiramisu/option.py b/tiramisu/option.py index 313299d..c7a28c2 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -25,8 +25,9 @@ import sys from copy import copy, deepcopy from types import FunctionType from IPy import IP +import warnings -from tiramisu.error import ConflictError +from tiramisu.error import ConfigError, ConflictError, ValueWarning from tiramisu.setting import groups, multitypes from tiramisu.i18n import _ from tiramisu.autolib import carry_out_calculation @@ -60,9 +61,8 @@ class BaseOption(object): __setattr__ method """ __slots__ = ('_name', '_requires', '_properties', '_readonly', - '_consistencies', '_calc_properties', '_impl_informations', - '_state_consistencies', '_state_readonly', '_state_requires', - '_stated') + '_calc_properties', '_impl_informations', + '_state_readonly', '_state_requires', '_stated') def __init__(self, name, doc, requires, properties): if not valid_name(name): @@ -72,7 +72,6 @@ class BaseOption(object): self.impl_set_information('doc', doc) self._calc_properties, self._requires = validate_requires_arg( requires, self._name) - self._consistencies = None if properties is None: properties = tuple() if not isinstance(properties, tuple): @@ -97,8 +96,7 @@ class BaseOption(object): "frozen" (which has noting to do with the high level "freeze" propertie or "read_only" property) """ - if not name.startswith('_state') and \ - name not in ('_cache_paths', '_consistencies'): + if not name.startswith('_state') and not name.startswith('_cache'): is_readonly = False # never change _name if name == '_name': @@ -108,15 +106,12 @@ class BaseOption(object): is_readonly = True except: pass - try: - if self._readonly is True: - if value is True: - # already readonly and try to re set readonly - # don't raise, just exit - return - is_readonly = True - except AttributeError: - pass + elif name != '_readonly': + try: + if self._readonly is True: + is_readonly = True + except AttributeError: + self._readonly = False if is_readonly: raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is" " read-only").format( @@ -148,56 +143,6 @@ class BaseOption(object): raise ValueError(_("information's item not found: {0}").format( key)) - # serialize/unserialize - def _impl_convert_consistencies(self, descr, load=False): - """during serialization process, many things have to be done. - one of them is the localisation of the options. - The paths are set once for all. - - :type descr: :class:`tiramisu.option.OptionDescription` - :param load: `True` if we are at the init of the option description - :type load: bool - """ - if not load and self._consistencies is None: - self._state_consistencies = None - elif load and self._state_consistencies is None: - self._consistencies = None - del(self._state_consistencies) - else: - if load: - consistencies = self._state_consistencies - else: - consistencies = self._consistencies - if isinstance(consistencies, list): - new_value = [] - for consistency in consistencies: - if load: - new_value.append((consistency[0], - descr.impl_get_opt_by_path( - consistency[1]))) - else: - new_value.append((consistency[0], - descr.impl_get_path_by_opt( - consistency[1]))) - - else: - new_value = {} - for key, _consistencies in consistencies.items(): - new_value[key] = [] - for key_cons, _cons in _consistencies: - _list_cons = [] - for _con in _cons: - if load: - _list_cons.append(descr.impl_get_opt_by_path(_con)) - else: - _list_cons.append(descr.impl_get_path_by_opt(_con)) - new_value[key].append((key_cons, tuple(_list_cons))) - if load: - del(self._state_consistencies) - self._consistencies = new_value - else: - self._state_consistencies = new_value - def _impl_convert_requires(self, descr, load=False): """export of the requires during the serialization process @@ -240,12 +185,10 @@ class BaseOption(object): :param descr: the parent :class:`tiramisu.option.OptionDescription` """ self._stated = True - self._impl_convert_consistencies(descr) - self._impl_convert_requires(descr) - try: - self._state_readonly = self._readonly - except AttributeError: - pass + for func in dir(self): + if func.startswith('_impl_convert_'): + getattr(self, func)(descr) + self._state_readonly = self._readonly def __getstate__(self, stated=True): """special method to enable the serialization with pickle @@ -265,7 +208,8 @@ class BaseOption(object): for subclass in self.__class__.__mro__: if subclass is not object: slots.update(subclass.__slots__) - slots -= frozenset(['_cache_paths', '__weakref__']) + slots -= frozenset(['_cache_paths', '_cache_consistencies', + '__weakref__']) states = {} for slot in slots: # remove variable if save variable converted @@ -292,8 +236,9 @@ class BaseOption(object): :type descr: :class:`tiramisu.option.OptionDescription` """ - self._impl_convert_consistencies(descr, load=True) - self._impl_convert_requires(descr, load=True) + for func in dir(self): + if func.startswith('_impl_convert_'): + getattr(self, func)(descr, load=True) try: self._readonly = self._state_readonly del(self._state_readonly) @@ -322,13 +267,15 @@ class Option(BaseOption): Reminder: an Option object is **not** a container for the value. """ __slots__ = ('_multi', '_validator', '_default_multi', '_default', - '_callback', '_multitype', '_master_slaves', '__weakref__') + '_state_callback', '_callback', '_multitype', + '_consistencies', '_warnings_only', '_master_slaves', + '_state_consistencies', '__weakref__') _empty = '' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_args=None, - properties=None): + callback_params=None, validator=None, validator_params=None, + properties=None, warnings_only=False): """ :param name: the option's name :param doc: the option's description @@ -344,18 +291,17 @@ class Option(BaseOption): :param callback_params: the callback's parameter :param validator: the name of a function which stands for a custom validation of the value - :param validator_args: the validator's parameters + :param validator_params: the validator's parameters :param properties: tuple of default properties + :param warnings_only: _validator and _consistencies don't raise if True + Values()._warning contain message """ super(Option, self).__init__(name, doc, requires, properties) self._multi = multi if validator is not None: - if type(validator) != FunctionType: - raise TypeError(_("validator must be a function")) - if validator_args is None: - validator_args = {} - self._validator = (validator, validator_args) + validate_callback(validator, validator_params, 'validator') + self._validator = (validator, validator_params) else: self._validator = None if not self._multi and default_multi is not None: @@ -377,11 +323,7 @@ class Option(BaseOption): "no callback defined" " yet for option {0}").format(name)) if callback is not None: - if type(callback) != FunctionType: - raise ValueError('callback must be a function') - if callback_params is not None and \ - not isinstance(callback_params, dict): - raise ValueError('callback_params must be a dict') + validate_callback(callback, callback_params, 'callback') self._callback = (callback, callback_params) else: self._callback = None @@ -390,101 +332,124 @@ class Option(BaseOption): default = [] self._multitype = multitypes.default self._default_multi = default_multi + self._warnings_only = warnings_only self.impl_validate(default) self._default = default + self._consistencies = None - def _launch_consistency(self, func, opt, vals, context, index, opt_): + def _launch_consistency(self, func, option, value, context, index, + all_cons_opts): + """Launch consistency now + + :param func: function name, this name should start with _cons_ + :type func: `str` + :param option: option that value is changing + :type option: `tiramisu.option.Option` + :param value: new value of this option + :param context: Config's context, if None, check default value instead + :type context: `tiramisu.config.Config` + :param index: only for multi option, consistency should be launch for + specified index + :type index: `int` + :param all_cons_opts: all options concerne by this consistency + :type all_cons_opts: `list` of `tiramisu.option.Option` + """ if context is not None: descr = context.cfgimpl_get_description() - if opt is self: - #values are for self, search opt_ values - values = vals - if context is not None: - path = descr.impl_get_path_by_opt(opt_) - values_ = context._getattr(path, validate=False) - else: - values_ = opt_.impl_getdefault() - if index is not None: - #value is not already set, could be higher - try: - values_ = values_[index] - except IndexError: - values_ = None - else: - #values are for opt_, search self values - values_ = vals - if context is not None: - path = descr.impl_get_path_by_opt(self) - values = context._getattr(path, validate=False) - else: - values = self.impl_getdefault() - if index is not None: - #value is not already set, could be higher - try: - values = values[index] - except IndexError: - values = None - if index is None and self.impl_is_multi(): - for index in range(0, len(values)): - try: - value = values[index] - value_ = values_[index] - except IndexError: - value = None - value_ = None - if None not in (value, value_): - getattr(self, func)(opt_._name, value, value_) - else: - if None not in (values, values_): - getattr(self, func)(opt_._name, values, values_) + #option is also in all_cons_opts + if option not in all_cons_opts: + raise ConfigError(_('option not in all_cons_opts')) - def impl_validate(self, value, context=None, validate=True): + all_cons_vals = [] + for opt in all_cons_opts: + #get value + if option == opt: + opt_value = value + else: + #if context, calculate value, otherwise get default value + if context is not None: + opt_value = context._getattr( + descr.impl_get_path_by_opt(opt), validate=False) + else: + opt_value = opt.impl_getdefault() + + #append value + if not self.impl_is_multi() or option == opt: + all_cons_vals.append(opt_value) + else: + #value is not already set, could be higher index + try: + all_cons_vals.append(opt_value[index]) + except IndexError: + #so return if no value + return + getattr(self, func)(all_cons_opts, all_cons_vals) + + def impl_validate(self, value, context=None, validate=True, + force_index=None): """ :param value: the option's value + :param context: Config's context + :type context: :class:`tiramisu.config.Config` :param validate: if true enables ``self._validator`` validation + :type validate: boolean + :param force_no_multi: if multi, value has to be a list + not if force_no_multi is True + :type force_no_multi: boolean """ if not validate: return def val_validator(val): if self._validator is not None: - callback_params = deepcopy(self._validator[1]) - callback_params.setdefault('', []).insert(0, val) - return carry_out_calculation(self._name, config=context, - callback=self._validator[0], - callback_params=callback_params) - else: - return True + if self._validator[1] is not None: + validator_params = deepcopy(self._validator[1]) + if '' in validator_params: + lst = list(validator_params['']) + lst.insert(0, val) + validator_params[''] = tuple(lst) + else: + validator_params[''] = (val,) + else: + validator_params = {'': (val,)} + # Raise ValueError if not valid + carry_out_calculation(self._name, config=context, + callback=self._validator[0], + callback_params=validator_params) def do_validation(_value, _index=None): if _value is None: - return True - if not val_validator(_value): - raise ValueError(_("invalid value {0} " - "for option {1} for object {2}" - ).format(_value, - self._name, - self.__class__.__name__)) + return + # option validation + self._validate(_value) try: - self._validate(_value) + # valid with self._validator + val_validator(_value) + # if not context launch consistency validation + if context is not None: + descr._valid_consistency(self, _value, context, _index) + self._second_level_validation(_value) except ValueError as err: - raise ValueError(_("invalid value {0} for option {1}: {2}" - "").format(_value, self._name, err)) - if context is not None: - descr._valid_consistency(self, _value, context, _index) + msg = _("invalid value {0} for option {1}: {2}").format( + _value, self._name, err) + if self._warnings_only: + warnings.warn_explicit(ValueWarning(msg, self), + ValueWarning, + self.__class__.__name__, 0) + else: + raise ValueError(msg) # generic calculation if context is not None: descr = context.cfgimpl_get_description() - if not self._multi: - do_validation(value) + + if not self._multi or force_index is not None: + do_validation(value, force_index) else: if not isinstance(value, list): - raise ValueError(_("invalid value {0} for option {1} " - "which must be a list").format(value, + raise ValueError(_("which must be a list").format(value, self._name)) - for index in range(0, len(value)): - val = value[index] + for index, val in enumerate(value): do_validation(val, index) def impl_getdefault(self, default_multi=False): @@ -529,29 +494,133 @@ class Option(BaseOption): def impl_is_multi(self): return self._multi - def impl_add_consistency(self, func, opt): + def impl_add_consistency(self, func, *other_opts): + """Add consistency means that value will be validate with other_opts + option's values. + + :param func: function's name + :type func: `str` + :param other_opts: options used to validate value + :type other_opts: `list` of `tiramisu.option.Option` + """ if self._consistencies is None: self._consistencies = [] - if not isinstance(opt, Option): - raise ValueError('consistency must be set with an option') - if self is opt: - raise ValueError('cannot add consistency with itself') - if self.impl_is_multi() != opt.impl_is_multi(): - raise ValueError('options in consistency' - ' should be multi in two sides') + for opt in other_opts: + if not isinstance(opt, Option): + raise ConfigError(_('consistency should be set with an option')) + if self is opt: + raise ConfigError(_('cannot add consistency with itself')) + if self.impl_is_multi() != opt.impl_is_multi(): + raise ConfigError(_('every options in consistency should be ' + 'multi or none')) func = '_cons_{0}'.format(func) - self._launch_consistency(func, - self, - self.impl_getdefault(), - None, None, opt) - self._consistencies.append((func, opt)) + all_cons_opts = tuple([self] + list(other_opts)) + value = self.impl_getdefault() + if value is not None: + if self.impl_is_multi(): + for idx, val in enumerate(value): + self._launch_consistency(func, self, val, None, + idx, all_cons_opts) + else: + self._launch_consistency(func, self, value, None, + None, all_cons_opts) + self._consistencies.append((func, all_cons_opts)) self.impl_validate(self.impl_getdefault()) - def _cons_not_equal(self, optname, value, value_): - if value == value_: - raise ValueError(_("invalid value {0} for option {1} " - "must be different as {2} option" - "").format(value, self._name, optname)) + def _cons_not_equal(self, opts, vals): + for idx_inf, val_inf in enumerate(vals): + for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]): + if val_inf == val_sup is not None: + raise ValueError(_("same value for {0} and {1}").format( + opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name)) + + def _impl_convert_callbacks(self, descr, load=False): + if not load and self._callback is None: + self._state_callback = None + elif load and self._state_callback is None: + self._callback = None + del(self._state_callback) + else: + if load: + callback, callback_params = self._state_callback + else: + callback, callback_params = self._callback + if callback_params is not None: + cllbck_prms = {} + for key, values in callback_params.items(): + vls = [] + for value in values: + if isinstance(value, tuple): + if load: + value = (descr.impl_get_opt_by_path(value[0]), + value[1]) + else: + value = (descr.impl_get_path_by_opt(value[0]), + value[1]) + vls.append(value) + cllbck_prms[key] = tuple(vls) + else: + cllbck_prms = None + + if load: + del(self._state_callback) + self._callback = (callback, cllbck_prms) + else: + self._state_callback = (callback, cllbck_prms) + + # serialize/unserialize + def _impl_convert_consistencies(self, descr, load=False): + """during serialization process, many things have to be done. + one of them is the localisation of the options. + The paths are set once for all. + + :type descr: :class:`tiramisu.option.OptionDescription` + :param load: `True` if we are at the init of the option description + :type load: bool + """ + if not load and self._consistencies is None: + self._state_consistencies = None + elif load and self._state_consistencies is None: + self._consistencies = None + del(self._state_consistencies) + else: + if load: + consistencies = self._state_consistencies + else: + consistencies = self._consistencies + if isinstance(consistencies, list): + new_value = [] + for consistency in consistencies: + values = [] + for obj in consistency[1]: + if load: + values.append(descr.impl_get_opt_by_path(obj)) + else: + values.append(descr.impl_get_path_by_opt(obj)) + new_value.append((consistency[0], tuple(values))) + + else: + new_value = {} + for key, _consistencies in consistencies.items(): + new_value[key] = [] + for key_cons, _cons in _consistencies: + _list_cons = [] + for _con in _cons: + if load: + _list_cons.append( + descr.impl_get_opt_by_path(_con)) + else: + _list_cons.append( + descr.impl_get_path_by_opt(_con)) + new_value[key].append((key_cons, tuple(_list_cons))) + if load: + del(self._state_consistencies) + self._consistencies = new_value + else: + self._state_consistencies = new_value + + def _second_level_validation(self, value): + pass class ChoiceOption(Option): @@ -566,7 +635,7 @@ class ChoiceOption(Option): def __init__(self, name, doc, values, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, open_values=False, validator=None, - validator_args=None, properties=()): + validator_params=None, properties=None, warnings_only=False): """ :param values: is a list of values the option can possibly take """ @@ -584,8 +653,9 @@ class ChoiceOption(Option): requires=requires, multi=multi, validator=validator, - validator_args=validator_args, - properties=properties) + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) def impl_get_values(self): return self._values @@ -662,7 +732,7 @@ class SymLinkOption(BaseOption): __slots__ = ('_name', '_opt', '_state_opt') _opt_type = 'symlink' #not return _opt consistencies - _consistencies = {} + _consistencies = None def __init__(self, name, opt): self._name = name @@ -688,23 +758,19 @@ class SymLinkOption(BaseOption): del(self._state_opt) super(SymLinkOption, self)._impl_setstate(descr) - def _impl_convert_consistencies(self, descr, load=False): - if load: - del(self._state_consistencies) - else: - self._state_consistencies = None - class IPOption(Option): "represents the choice of an ip" - __slots__ = ('_only_private',) + __slots__ = ('_private_only', '_allow_reserved') _opt_type = 'ip' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_args=None, - properties=None, only_private=False): - self._only_private = only_private + callback_params=None, validator=None, validator_params=None, + properties=None, private_only=False, allow_reserved=False, + warnings_only=False): + self._private_only = private_only + self._allow_reserved = allow_reserved super(IPOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, @@ -712,14 +778,21 @@ class IPOption(Option): requires=requires, multi=multi, validator=validator, - validator_args=validator_args, - properties=properties) + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) def _validate(self, value): + try: + IP('{0}/32'.format(value)) + except ValueError: + raise ValueError(_('invalid IP {0}').format(self._name)) + + def _second_level_validation(self, value): ip = IP('{0}/32'.format(value)) - if ip.iptype() == 'RESERVED': + if not self._allow_reserved and ip.iptype() == 'RESERVED': raise ValueError(_("IP mustn't not be in reserved class")) - if self._only_private and not ip.iptype() == 'PRIVATE': + if self._private_only and not ip.iptype() == 'PRIVATE': raise ValueError(_("IP must be in private class")) @@ -738,10 +811,10 @@ class PortOption(Option): def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_args=None, + callback_params=None, validator=None, validator_params=None, properties=None, allow_range=False, allow_zero=False, allow_wellknown=True, allow_registred=True, - allow_private=False): + allow_private=False, warnings_only=False): self._allow_range = allow_range self._min_value = None self._max_value = None @@ -772,8 +845,9 @@ class PortOption(Option): requires=requires, multi=multi, validator=validator, - validator_args=validator_args, - properties=properties) + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) def _validate(self, value): if self._allow_range and ":" in str(value): @@ -798,9 +872,15 @@ class NetworkOption(Option): _opt_type = 'network' def _validate(self, value): + try: + IP(value) + except ValueError: + raise ValueError(_('invalid network address {0}').format(self._name)) + + def _second_level_validation(self, value): ip = IP(value) if ip.iptype() == 'RESERVED': - raise ValueError(_("network mustn't not be in reserved class")) + raise ValueError(_("network shall not be in reserved class")) class NetmaskOption(Option): @@ -809,18 +889,26 @@ class NetmaskOption(Option): _opt_type = 'netmask' def _validate(self, value): - IP('0.0.0.0/{0}'.format(value)) + try: + IP('0.0.0.0/{0}'.format(value)) + except ValueError: + raise ValueError(_('invalid netmask address {0}').format(self._name)) - def _cons_network_netmask(self, optname, value, value_): + def _cons_network_netmask(self, opts, vals): #opts must be (netmask, network) options - self.__cons_netmask(optname, value, value_, False) + if None in vals: + return + self.__cons_netmask(opts, vals[0], vals[1], False) - def _cons_ip_netmask(self, optname, value, value_): + def _cons_ip_netmask(self, opts, vals): #opts must be (netmask, ip) options - self.__cons_netmask(optname, value, value_, True) + if None in vals: + return + self.__cons_netmask(opts, vals[0], vals[1], True) - #def __cons_netmask(self, opt, value, context, index, opts, make_net): - def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net): + def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net): + if len(opts) != 2: + raise ConfigError(_('invalid len for opts')) msg = None try: ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), @@ -846,23 +934,48 @@ class NetmaskOption(Option): else: msg = _("invalid network {0} ({1}) with netmask {2} ({3})") if msg is not None: - raise ValueError(msg.format(val_ipnetwork, optname, + raise ValueError(msg.format(val_ipnetwork, opts[1]._name, val_netmask, self._name)) +class BroadcastOption(Option): + __slots__ = tuple() + _opt_type = 'broadcast' + + def _validate(self, value): + try: + IP('{0}/32'.format(value)) + except ValueError: + raise ValueError(_('invalid broadcast address {0}').format(self._name)) + + def _cons_broadcast(self, opts, vals): + if len(vals) != 3: + raise ConfigError(_('invalid len for vals')) + if None in vals: + return + broadcast, network, netmask = vals + if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast): + raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' + '({3}) and netmask {4} ({5})').format( + broadcast, opts[0]._name, network, + opts[1]._name, netmask, opts[2]._name)) + + class DomainnameOption(Option): - "represents the choice of a domain name" + """represents the choice of a domain name + netbios: for MS domain + hostname: to identify the device + domainname: + fqdn: with tld, not supported yet + """ __slots__ = ('_type', '_allow_ip') _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_args=None, - properties=None, allow_ip=False, type_='domainname'): - #netbios: for MS domain - #hostname: to identify the device - #domainname: - #fqdn: with tld, not supported yet + callback_params=None, validator=None, validator_params=None, + properties=None, allow_ip=False, type_='domainname', + warnings_only=False): if type_ not in ['netbios', 'hostname', 'domainname']: raise ValueError(_('unknown type_ {0} for hostname').format(type_)) self._type = type_ @@ -876,8 +989,9 @@ class DomainnameOption(Option): requires=requires, multi=multi, validator=validator, - validator_args=validator_args, - properties=properties) + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) def _validate(self, value): if self._allow_ip is True: @@ -915,9 +1029,9 @@ class OptionDescription(BaseOption): """ __slots__ = ('_name', '_requires', '_cache_paths', '_group_type', '_state_group_type', '_properties', '_children', - '_consistencies', '_calc_properties', '__weakref__', + '_cache_consistencies', '_calc_properties', '__weakref__', '_readonly', '_impl_informations', '_state_requires', - '_state_consistencies', '_stated', '_state_readonly') + '_stated', '_state_readonly') _opt_type = 'optiondescription' def __init__(self, name, doc, children, requires=None, properties=None): @@ -938,6 +1052,7 @@ class OptionDescription(BaseOption): old = child self._children = (tuple(child_names), tuple(children)) self._cache_paths = None + self._cache_consistencies = None # the group_type is useful for filtering OptionDescriptions in a config self._group_type = groups.default @@ -1011,12 +1126,11 @@ class OptionDescription(BaseOption): if not force_no_consistencies and \ option._consistencies is not None: for consistency in option._consistencies: - func, opt = consistency - opts = (option, opt) - _consistencies.setdefault(opt, - []).append((func, opts)) - _consistencies.setdefault(option, - []).append((func, opts)) + func, all_cons_opts = consistency + for opt in all_cons_opts: + _consistencies.setdefault(opt, + []).append((func, + all_cons_opts)) else: _currpath.append(attr) option.impl_build_cache(cache_path, @@ -1028,7 +1142,12 @@ class OptionDescription(BaseOption): if save: self._cache_paths = (tuple(cache_option), tuple(cache_path)) if not force_no_consistencies: - self._consistencies = _consistencies + if _consistencies != {}: + self._cache_consistencies = {} + for opt, cons in _consistencies.items(): + if opt not in cache_option: + raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name)) + self._cache_consistencies[opt] = tuple(cons) self._readonly = True def impl_get_opt_by_path(self, path): @@ -1099,17 +1218,18 @@ class OptionDescription(BaseOption): def impl_get_group_type(self): return self._group_type - def _valid_consistency(self, opt, value, context=None, index=None): - consistencies = self._consistencies.get(opt) + def _valid_consistency(self, option, value, context, index): + if self._cache_consistencies is None: + return True + #consistencies is something like [('_cons_not_equal', (opt1, opt2))] + consistencies = self._cache_consistencies.get(option) if consistencies is not None: - for consistency in consistencies: - opt_ = consistency[1] - ret = opt_[0]._launch_consistency(consistency[0], - opt, - value, - context, - index, - opt_[1]) + for func, all_cons_opts in consistencies: + #all_cons_opts[0] is the option where func is set + ret = all_cons_opts[0]._launch_consistency(func, option, + value, + context, index, + all_cons_opts) if ret is False: return False return True @@ -1149,6 +1269,7 @@ class OptionDescription(BaseOption): """ if descr is None: self._cache_paths = None + self._cache_consistencies = None self.impl_build_cache(force_no_consistencies=True) descr = self self._group_type = getattr(groups, self._state_group_type) @@ -1252,3 +1373,34 @@ def validate_requires_arg(requires, name): require[3], require[4], require[5])) ret.append(tuple(ret_action)) return frozenset(config_action.keys()), tuple(ret) + + +def validate_callback(callback, callback_params, type_): + if type(callback) != FunctionType: + raise ValueError(_('{0} should be a function').format(type_)) + if callback_params is not None: + if not isinstance(callback_params, dict): + raise ValueError(_('{0}_params should be a dict').format(type_)) + for key, callbacks in callback_params.items(): + if key != '' and len(callbacks) != 1: + raise ValueError(_('{0}_params with key {1} should not have ' + 'length different to 1').format(type_, + key)) + if not isinstance(callbacks, tuple): + raise ValueError(_('{0}_params should be tuple for key "{1}"' + ).format(type_, key)) + for callbk in callbacks: + if isinstance(callbk, tuple): + option, force_permissive = callbk + if type_ == 'validator' and not force_permissive: + raise ValueError(_('validator not support tuple')) + if not isinstance(option, Option) and not \ + isinstance(option, SymLinkOption): + raise ValueError(_('{0}_params should have an option ' + 'not a {0} for first argument' + ).format(type_, type(option))) + if force_permissive not in [True, False]: + raise ValueError(_('{0}_params should have a boolean' + ' not a {0} for second argument' + ).format(type_, type( + force_permissive))) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 249af37..684ec74 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -105,7 +105,7 @@ rw_remove = set(['permissive', 'everything_frozen', 'mandatory']) # ____________________________________________________________ -class _NameSpace: +class _NameSpace(object): """convenient class that emulates a module and builds constants (that is, unique names) when attribute is added, we cannot delete it @@ -385,13 +385,17 @@ class Settings(object): #____________________________________________________________ def validate_properties(self, opt_or_descr, is_descr, is_write, path, value=None, force_permissive=False, - force_properties=None): + force_properties=None, force_permissives=None): """ validation upon the properties related to `opt_or_descr` :param opt_or_descr: an option or an option description object :param force_permissive: behaves as if the permissive property was present + :param force_properties: set() with properties that is force to add + in global properties + :param force_permissives: set() with permissives that is force to add + in global permissives :param is_descr: we have to know if we are in an option description, just because the mandatory property doesn't exist here @@ -408,6 +412,8 @@ class Settings(object): 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: + properties -= force_permissives # global properties if force_properties is not None: @@ -585,3 +591,23 @@ class Settings(object): :returns: path """ return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt) + + def get_modified_properties(self): + return self._p_.get_modified_properties() + + def get_modified_permissives(self): + return self._p_.get_modified_permissives() + + def __getstate__(self): + return {'_p_': self._p_, '_owner': str(self._owner)} + + def _impl_setstate(self, storage): + self._p_._storage = storage + + def __setstate__(self, states): + self._p_ = states['_p_'] + try: + self._owner = getattr(owners, states['_owner']) + except AttributeError: + owners.addowner(states['_owner']) + self._owner = getattr(owners, states['_owner']) diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index 1394258..c232472 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -68,7 +68,7 @@ class StorageType(object): storage_type = StorageType() -def set_storage(name, **args): +def set_storage(name, **kwargs): """Change storage's configuration :params name: is the storage name. If storage is already set, cannot @@ -77,16 +77,31 @@ def set_storage(name, **args): Other attributes are differents according to the selected storage's name """ storage_type.set(name) - settings = storage_type.get().Setting() - for option, value in args.items(): + setting = storage_type.get().setting + for option, value in kwargs.items(): try: - getattr(settings, option) - setattr(settings, option, value) + getattr(setting, option) + setattr(setting, option, value) except AttributeError: raise ValueError(_('option {0} not already exists in storage {1}' '').format(option, name)) +def _impl_getstate_setting(): + setting = storage_type.get().setting + state = {'name': storage_type.storage_type} + for var in dir(setting): + if not var.startswith('_'): + state[var] = getattr(setting, var) + return state + + +def get_storage(session_id, persistent, test): + """all used when __setstate__ a Config + """ + return storage_type.get().Storage(session_id, persistent, test) + + def get_storages(context, session_id, persistent): def gen_id(config): return str(id(config)) + str(time()) diff --git a/tiramisu/storage/dictionary/__init__.py b/tiramisu/storage/dictionary/__init__.py index dadce23..bc81450 100644 --- a/tiramisu/storage/dictionary/__init__.py +++ b/tiramisu/storage/dictionary/__init__.py @@ -26,6 +26,6 @@ use it. But if something goes wrong, you will lost your modifications. """ from .value import Values from .setting import Settings -from .storage import Setting, Storage, list_sessions, delete_session +from .storage import setting, Storage, list_sessions, delete_session -__all__ = (Setting, Values, Settings, Storage, list_sessions, delete_session) +__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session) diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 706ab2a..1b7001b 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -17,7 +17,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ -from ..cache import Cache +from ..util import Cache class Settings(Cache): @@ -50,12 +50,21 @@ class Settings(Cache): except KeyError: pass - def get_properties(self, context): - return self._properties - # permissive def setpermissive(self, path, permissive): self._permissives[path] = frozenset(permissive) def getpermissive(self, path=None): return self._permissives.get(path, frozenset()) + + def get_modified_properties(self): + """return all modified settings in a dictionary + example: {'path1': set(['prop1', 'prop2'])} + """ + return self._properties + + def get_modified_permissives(self): + """return all modified permissives in a dictionary + example: {'path1': set(['perm1', 'perm2'])} + """ + return self._permissives diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 6e15c1b..465fe26 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -18,9 +18,10 @@ # ____________________________________________________________ from tiramisu.i18n import _ from tiramisu.error import ConfigError +from ..util import SerializeObject -class Setting(object): +class Setting(SerializeObject): """Dictionary storage has no particular setting. """ pass @@ -39,15 +40,18 @@ def delete_session(session_id): class Storage(object): - __slots__ = ('session_id', 'values', 'settings') + __slots__ = ('session_id', 'persistent') storage = 'dictionary' + #if object could be serializable + serializable = True - def __init__(self, session_id, persistent): - if session_id in _list_sessions: + def __init__(self, session_id, persistent, test=False): + if not test and session_id in _list_sessions: raise ValueError(_('session already used')) if persistent: raise ValueError(_('a dictionary cannot be persistent')) self.session_id = session_id + self.persistent = persistent _list_sessions.append(self.session_id) def __del__(self): diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index c435d06..fedf1ec 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -18,7 +18,7 @@ # # ____________________________________________________________ -from ..cache import Cache +from ..util import Cache class Values(Cache): diff --git a/tiramisu/storage/sqlite3/__init__.py b/tiramisu/storage/sqlite3/__init__.py index dc6c14b..8d79070 100644 --- a/tiramisu/storage/sqlite3/__init__.py +++ b/tiramisu/storage/sqlite3/__init__.py @@ -24,6 +24,6 @@ You should not configure differents Configs with same session_id. """ from .value import Values from .setting import Settings -from .storage import Setting, Storage, list_sessions, delete_session +from .storage import setting, Storage, list_sessions, delete_session -__all__ = (Setting, Values, Settings, Storage, list_sessions, delete_session) +__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session) diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index 720849b..ed79181 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -30,22 +30,22 @@ class Settings(Sqlite3DB): permissives_table += 'primary key, permissives text)' # should init cache too super(Settings, self).__init__(storage) - self.storage.execute(settings_table, commit=False) - self.storage.execute(permissives_table) + self._storage.execute(settings_table, commit=False) + self._storage.execute(permissives_table) # propertives def setproperties(self, path, properties): path = self._sqlite_encode_path(path) - self.storage.execute("DELETE FROM property WHERE path = ?", (path,), - False) - self.storage.execute("INSERT INTO property(path, properties) VALUES " - "(?, ?)", (path, - self._sqlite_encode(properties))) + self._storage.execute("DELETE FROM property WHERE path = ?", (path,), + False) + self._storage.execute("INSERT INTO property(path, properties) VALUES " + "(?, ?)", (path, + self._sqlite_encode(properties))) def getproperties(self, path, default_properties): path = self._sqlite_encode_path(path) - value = self.storage.select("SELECT properties FROM property WHERE " - "path = ?", (path,)) + value = self._storage.select("SELECT properties FROM property WHERE " + "path = ?", (path,)) if value is None: return set(default_properties) else: @@ -53,42 +53,53 @@ class Settings(Sqlite3DB): def hasproperties(self, path): path = self._sqlite_encode_path(path) - return self.storage.select("SELECT properties FROM property WHERE " - "path = ?", (path,)) is not None + return self._storage.select("SELECT properties FROM property WHERE " + "path = ?", (path,)) is not None def reset_all_propertives(self): - self.storage.execute("DELETE FROM property") + self._storage.execute("DELETE FROM property") def reset_properties(self, path): path = self._sqlite_encode_path(path) - self.storage.execute("DELETE FROM property WHERE path = ?", (path,)) - - def get_properties(self, context): - """return all properties in a dictionary - """ - ret = {} - for path, properties in self.storage.select("SELECT * FROM property", - only_one=False): - path = self._sqlite_decode_path(path) - properties = self._sqlite_decode(properties) - ret[path] = properties - return ret + self._storage.execute("DELETE FROM property WHERE path = ?", (path,)) # permissive def setpermissive(self, path, permissive): path = self._sqlite_encode_path(path) - self.storage.execute("DELETE FROM permissive WHERE path = ?", (path,), - False) - self.storage.execute("INSERT INTO permissive(path, permissives) " - "VALUES (?, ?)", (path, - self._sqlite_encode(permissive) - )) + self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,), + False) + self._storage.execute("INSERT INTO permissive(path, permissives) " + "VALUES (?, ?)", (path, + self._sqlite_encode(permissive) + )) def getpermissive(self, path='_none'): - permissives = self.storage.select("SELECT permissives FROM " - "permissive WHERE path = ?", - (path,)) + permissives = self._storage.select("SELECT permissives FROM " + "permissive WHERE path = ?", + (path,)) if permissives is None: return frozenset() else: return frozenset(self._sqlite_decode(permissives[0])) + + def get_modified_properties(self): + """return all modified settings in a dictionary + example: {'path1': set(['prop1', 'prop2'])} + """ + ret = {} + for path, properties in self._storage.select("SELECT * FROM property", + only_one=False): + path = self._sqlite_decode_path(path) + ret[path] = self._sqlite_decode(properties) + return ret + + def get_modified_permissives(self): + """return all modified permissives in a dictionary + example: {'path1': set(['perm1', 'perm2'])} + """ + ret = {} + for path, permissives in self._storage.select("SELECT * FROM permissive", + only_one=False): + path = self._sqlite_decode_path(path) + ret[path] = self._sqlite_decode(permissives) + return ret diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py index 9a967cd..68f2886 100644 --- a/tiramisu/storage/sqlite3/sqlite3db.py +++ b/tiramisu/storage/sqlite3/sqlite3db.py @@ -17,8 +17,11 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ -from cPickle import loads, dumps -from ..cache import Cache +try: + from cPickle import loads, dumps +except ImportError: + from pickle import loads, dumps +from ..util import Cache class Sqlite3DB(Cache): diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index 2ab8e08..3b4f265 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -22,9 +22,10 @@ from os import unlink from os.path import basename, splitext, join import sqlite3 from glob import glob +from ..util import SerializeObject -class Setting(object): +class Setting(SerializeObject): """:param extension: database file extension (by default: db) :param dir_database: root database directory (by default: /tmp) """ @@ -52,13 +53,17 @@ def delete_session(session_id): class Storage(object): - __slots__ = ('_conn', '_cursor', 'persistent', '_session_id') + __slots__ = ('_conn', '_cursor', 'persistent', 'session_id', 'serializable') storage = 'sqlite3' - def __init__(self, session_id, persistent): + def __init__(self, session_id, persistent, test=False): self.persistent = persistent - self._session_id = session_id - self._conn = sqlite3.connect(_gen_filename(self._session_id)) + if self.persistent: + self.serializable = True + else: + self.serializable = False + self.session_id = session_id + self._conn = sqlite3.connect(_gen_filename(self.session_id)) self._conn.text_factory = str self._cursor = self._conn.cursor() @@ -80,4 +85,4 @@ class Storage(object): self._cursor.close() self._conn.close() if not self.persistent: - delete_session(self._session_id) + delete_session(self.session_id) diff --git a/tiramisu/storage/sqlite3/value.py b/tiramisu/storage/sqlite3/value.py index 3f76e2c..672ecab 100644 --- a/tiramisu/storage/sqlite3/value.py +++ b/tiramisu/storage/sqlite3/value.py @@ -32,11 +32,11 @@ class Values(Sqlite3DB): super(Values, self).__init__(storage) values_table = 'CREATE TABLE IF NOT EXISTS value(path text primary ' values_table += 'key, value text, owner text)' - self.storage.execute(values_table, commit=False) + self._storage.execute(values_table, commit=False) informations_table = 'CREATE TABLE IF NOT EXISTS information(key text primary ' informations_table += 'key, value text)' - self.storage.execute(informations_table) - for owner in self.storage.select("SELECT DISTINCT owner FROM value", tuple(), False): + self._storage.execute(informations_table) + for owner in self._storage.select("SELECT DISTINCT owner FROM value", tuple(), False): try: getattr(owners, owner[0]) except AttributeError: @@ -44,7 +44,7 @@ class Values(Sqlite3DB): # sqlite def _sqlite_select(self, path): - return self.storage.select("SELECT value FROM value WHERE path = ?", + return self._storage.select("SELECT value FROM value WHERE path = ?", (path,)) # value @@ -54,7 +54,7 @@ class Values(Sqlite3DB): """ self.resetvalue(path) path = self._sqlite_encode_path(path) - self.storage.execute("INSERT INTO value(path, value, owner) VALUES " + self._storage.execute("INSERT INTO value(path, value, owner) VALUES " "(?, ?, ?)", (path, self._sqlite_encode(value), str(owner))) @@ -76,14 +76,14 @@ class Values(Sqlite3DB): """remove value means delete value in storage """ path = self._sqlite_encode_path(path) - self.storage.execute("DELETE FROM value WHERE path = ?", (path,)) + self._storage.execute("DELETE FROM value WHERE path = ?", (path,)) def get_modified_values(self): """return all values in a dictionary example: {option1: (owner, 'value1'), option2: (owner, 'value2')} """ ret = {} - for path, value, owner in self.storage.select("SELECT * FROM value", + for path, value, owner in self._storage.select("SELECT * FROM value", only_one=False): path = self._sqlite_decode_path(path) owner = getattr(owners, owner) @@ -97,7 +97,7 @@ class Values(Sqlite3DB): """change owner for an option """ path = self._sqlite_encode_path(path) - self.storage.execute("UPDATE value SET owner = ? WHERE path = ?", + self._storage.execute("UPDATE value SET owner = ? WHERE path = ?", (str(owner), path)) def getowner(self, path, default): @@ -105,7 +105,7 @@ class Values(Sqlite3DB): return: owner object """ path = self._sqlite_encode_path(path) - owner = self.storage.select("SELECT owner FROM value WHERE path = ?", + owner = self._storage.select("SELECT owner FROM value WHERE path = ?", (path,)) if owner is None: return default @@ -125,9 +125,9 @@ class Values(Sqlite3DB): :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ - self.storage.execute("DELETE FROM information WHERE key = ?", (key,), + self._storage.execute("DELETE FROM information WHERE key = ?", (key,), False) - self.storage.execute("INSERT INTO information(key, value) VALUES " + self._storage.execute("INSERT INTO information(key, value) VALUES " "(?, ?)", (key, self._sqlite_encode(value))) def get_information(self, key): @@ -135,7 +135,7 @@ class Values(Sqlite3DB): :param key: the item string (ex: "help") """ - value = self.storage.select("SELECT value FROM information WHERE key = ?", + value = self._storage.select("SELECT value FROM information WHERE key = ?", (key,)) if value is None: raise ValueError("not found") diff --git a/tiramisu/storage/cache.py b/tiramisu/storage/util.py similarity index 51% rename from tiramisu/storage/cache.py rename to tiramisu/storage/util.py index 347d270..68482e6 100644 --- a/tiramisu/storage/cache.py +++ b/tiramisu/storage/util.py @@ -17,15 +17,65 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ +from tiramisu.setting import owners + + +class SerializeObject(object): + def __getstate__(self): + ret = {} + for key in dir(self): + if not key.startswith('__'): + ret[key] = getattr(self, key) + return ret class Cache(object): - __slots__ = ('_cache', 'storage') + __slots__ = ('_cache', '_storage') key_is_path = False def __init__(self, storage): self._cache = {} - self.storage = storage + self._storage = storage + + def __getstate__(self): + slots = set() + for subclass in self.__class__.__mro__: + if subclass is not object: + slots.update(subclass.__slots__) + slots -= frozenset(['__weakref__', '_storage']) + states = {} + for slot in slots: + try: + value = getattr(self, slot) + #value has owners object, need 'str()' it + if slot == '_values': + _value = {} + for key, values in value.items(): + vals = list(values) + vals[0] = str(vals[0]) + _value[key] = tuple(vals) + states[slot] = _value + else: + states[slot] = value + except AttributeError: + pass + return states + + def __setstate__(self, states): + for key, value in states.items(): + #value has owners object, need to reconstruct it + if key == '_values': + _value = {} + for key_, values_ in value.items(): + vals = list(values_) + try: + vals[0] = getattr(owners, vals[0]) + except AttributeError: + owners.addowner(vals[0]) + vals[0] = getattr(owners, vals[0]) + _value[key_] = tuple(vals) + value = _value + setattr(self, key, value) def setcache(self, path, val, time): self._cache[path] = (val, time) diff --git a/tiramisu/value.py b/tiramisu/value.py index ffd34d6..4426742 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -106,7 +106,8 @@ class Values(object): path = self._get_opt_path(opt) if self._p_.hasvalue(path): setting = self.context().cfgimpl_get_settings() - opt.impl_validate(opt.impl_getdefault(), self.context(), + opt.impl_validate(opt.impl_getdefault(), + self.context(), 'validator' in setting) self.context().cfgimpl_reset_cache() if (opt.impl_is_multi() and @@ -124,7 +125,7 @@ class Values(object): return True return False - def _getcallback_value(self, opt, index=None): + def _getcallback_value(self, opt, index=None, max_len=None): """ retrieves a value for the options that have a callback @@ -139,7 +140,7 @@ class Values(object): return carry_out_calculation(opt._name, config=self.context(), callback=callback, callback_params=callback_params, - index=index) + index=index, max_len=max_len) def __getitem__(self, opt): "enables us to use the pythonic dictionary-like access to values" @@ -177,11 +178,20 @@ class Values(object): # options with callbacks setting = self.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 + # _getcallback_value raise a ConfigError. + # We can not raise ConfigError if this option should raise + # PropertiesOptionError too. So we get config_error and raise + # ConfigError if properties did not raise. + config_error = None + force_permissives = None # if value is callback and is not set # or frozen with force_default_on_freeze if opt.impl_has_callback() and ( self._is_default_owner(path) or (is_frozen and 'force_default_on_freeze' in setting[opt])): + lenmaster = None no_value_slave = False if (opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.slave): @@ -193,15 +203,25 @@ class Values(object): no_value_slave = True if not no_value_slave: - value = self._getcallback_value(opt) - if (opt.impl_is_multi() and - opt.impl_get_multitype() == multitypes.slave): - if not isinstance(value, list): - value = [value for i in range(lenmaster)] - if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path, validate) - # suppress value if already set - self.reset(opt, path) + try: + value = self._getcallback_value(opt, max_len=lenmaster) + except ConfigError as err: + # cannot assign config_err directly in python 3.3 + config_error = err + value = None + # should not raise PropertiesOptionError if option is + # mandatory + force_permissives = set(['mandatory']) + else: + if (opt.impl_is_multi() and + opt.impl_get_multitype() == multitypes.slave): + if not isinstance(value, list): + value = [value for i in range(lenmaster)] + if config_error is None: + if opt.impl_is_multi(): + value = Multi(value, self.context, opt, path, validate) + # suppress value if already set + self.reset(opt, path) # frozen and force default elif is_frozen and 'force_default_on_freeze' in setting[opt]: value = self._getdefault(opt) @@ -209,15 +229,18 @@ class Values(object): value = Multi(value, self.context, opt, path, validate) else: value = self._getvalue(opt, path, validate) - if validate: + if config_error is None and validate: opt.impl_validate(value, self.context(), 'validator' in setting) - if self._is_default_owner(path) and \ + 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) if validate_properties: setting.validate_properties(opt, False, False, value=value, path=path, force_permissive=force_permissive, - force_properties=force_properties) + force_properties=force_properties, + force_permissives=force_permissives) + if config_error is not None: + raise config_error return value def __setitem__(self, opt, value): @@ -231,7 +254,7 @@ class Values(object): opt.impl_validate(value, self.context(), 'validator' in self.context().cfgimpl_get_settings()) if opt.impl_is_multi() and not isinstance(value, Multi): - value = Multi(value, self.context, opt, path) + value = Multi(value, self.context, opt, path, setitem=True) self._setvalue(opt, path, value, force_permissive=force_permissive, is_write=is_write) @@ -339,6 +362,15 @@ class Values(object): raise ValueError(_("information's item" " not found: {0}").format(key)) + def __getstate__(self): + return {'_p_': self._p_} + + def _impl_setstate(self, storage): + self._p_._storage = storage + + def __setstate__(self, states): + self._p_ = states['_p_'] + # ____________________________________________________________ # multi types @@ -349,11 +381,13 @@ class Multi(list): that support item notation for the values of multi options""" __slots__ = ('opt', 'path', 'context') - def __init__(self, value, context, opt, path, validate=True): + def __init__(self, value, context, opt, path, validate=True, + setitem=False): """ :param value: the Multi wraps a list value :param context: the home config that has the values :param opt: the option object that have this Multi value + :param setitem: only if set a value """ self.opt = opt self.path = path @@ -363,27 +397,35 @@ class Multi(list): if not isinstance(value, list): value = [value] if validate and self.opt.impl_get_multitype() == multitypes.slave: - value = self._valid_slave(value) - elif self.opt.impl_get_multitype() == multitypes.master: + value = self._valid_slave(value, setitem) + elif validate and self.opt.impl_get_multitype() == multitypes.master: self._valid_master(value) super(Multi, self).__init__(value) - def _valid_slave(self, value): + 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( self.opt.impl_get_master_slaves()) mastervalue = getattr(self.context(), masterp) 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 - not self.context().cfgimpl_get_values( - )._is_default_owner(self.path)): + is_default_owner): raise SlaveError(_("invalid len for the slave: {0}" " which has {1} as master").format( self.opt._name, masterp)) elif valuelen < masterlen: for num in range(0, masterlen - valuelen): - value.append(self.opt.impl_getdefault_multi()) + if self.opt.impl_has_callback(): + # if callback add a value, but this value will not change + # anymore automaticly (because this value has owner) + index = value.__len__() + value.append(values._getcallback_value(self.opt, + index=index)) + else: + value.append(self.opt.impl_getdefault_multi()) #else: same len so do nothing return value @@ -401,13 +443,22 @@ class Multi(list): self.opt._name, slave._name)) elif len(value_slave) < masterlen: for num in range(0, masterlen - len(value_slave)): - value_slave.append(slave.impl_getdefault_multi(), - force=True) + 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) - def __setitem__(self, key, value): - self._validate(value) + def __setitem__(self, index, value): + self._validate(value, index) #assume not checking mandatory property - super(Multi, self).__setitem__(key, value) + super(Multi, self).__setitem__(index, value) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) def append(self, value, force=False): @@ -425,15 +476,17 @@ class Multi(list): #Force None il return a list if isinstance(value, list): value = None - self._validate(value) + index = self.__len__() + self._validate(value, index) super(Multi, self).append(value) - self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force) + self.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) if not values._is_default_owner(path): if slave.impl_has_callback(): - index = self.__len__() - 1 dvalue = values._getcallback_value(slave, index=index) else: dvalue = slave.impl_getdefault_multi() @@ -485,22 +538,26 @@ class Multi(list): super(Multi, self).extend(iterable) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) - def _validate(self, value): + def _validate(self, value, force_index): if value is not None: try: - self.opt._validate(value) + self.opt.impl_validate(value, context=self.context(), + force_index=force_index) except ValueError as err: raise ValueError(_("invalid value {0} " "for option {1}: {2}" "").format(str(value), self.opt._name, err)) - def pop(self, key, force=False): + def pop(self, index, force=False): """the list value can be updated (poped) only if the option is a master - :param key: index of the element to pop - :return: the requested element + :param index: remove item a index + :type index: int + :param force: force pop item (withoud check master/slave) + :type force: boolean + :returns: item at index """ if not force: if self.opt.impl_get_multitype() == multitypes.slave: @@ -513,8 +570,8 @@ class Multi(list): #get multi without valid properties values.getitem(slave, validate_properties=False - ).pop(key, force=True) + ).pop(index, force=True) #set value without valid properties - ret = super(Multi, self).pop(key) + ret = super(Multi, self).pop(index) self.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 fd00df1..256a0da 100644 --- a/translations/fr/tiramisu.po +++ b/translations/fr/tiramisu.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-08-31 09:52+CEST\n" +"POT-Creation-Date: 2013-09-28 19:06+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Emmanuel Garette \n" "Language-Team: LANGUAGE \n" @@ -11,438 +11,526 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" -#: tiramisu/autolib.py:58 -msgid "no config specified but needed" -msgstr "aucune config spécifié alors que c'est nécessaire" - -#: tiramisu/autolib.py:65 +#: tiramisu/autolib.py:144 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:74 +#: 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, valeur d'un option avec le type multi doit " -"avoir la même longueur pour : {0}" +"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:47 +#: tiramisu/config.py:51 msgid "descr must be an optiondescription, not {0}" msgstr "descr doit être une optiondescription pas un {0}" -#: tiramisu/config.py:121 +#: tiramisu/config.py:126 msgid "unknown group_type: {0}" msgstr "group_type inconnu: {0}" -#: tiramisu/config.py:157 +#: tiramisu/config.py:162 msgid "" "no option description found for this config (may be metaconfig without meta)" msgstr "" -"pas d'option description pour cette config (peut être une metaconfig sans " -"meta)" +"pas d'option description trouvé pour cette config (peut être une metaconfig " +"sans meta)" -#: tiramisu/config.py:311 +#: tiramisu/config.py:188 +msgid "can't assign to an OptionDescription" +msgstr "ne peut pas attribuer une valeur à une OptionDescription" + +#: tiramisu/config.py:319 msgid "unknown type_ type {0}for _find" msgstr "type_ type {0} pour _find inconnu" -#: tiramisu/config.py:350 +#: tiramisu/config.py:358 msgid "no option found in config with these criteria" msgstr "aucune option trouvée dans la config avec ces critères" -#: tiramisu/config.py:400 +#: tiramisu/config.py:408 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:421 +#: tiramisu/config.py:429 msgid "unexpected path {0}, should start with {1}" msgstr "chemin imprévu {0}, devrait commencer par {1}" -#: tiramisu/config.py:481 +#: tiramisu/config.py:489 msgid "opt in getowner must be an option not {0}" msgstr "opt dans getowner doit être une option pas {0}" -#: tiramisu/option.py:71 -msgid "{0} has no attribute impl_set_information" -msgstr "{0} n'a pas d'attribut impl_set_information" - -#: tiramisu/option.py:86 -msgid "information's item not found: {0}" -msgstr "aucune config spécifié alors que c'est nécessaire" - -#: tiramisu/option.py:89 -msgid "{0} has no attribute impl_get_information" -msgstr "{0} n'a pas d'attribut impl_get_information" - -#: tiramisu/option.py:117 -msgid "'{0}' ({1}) object attribute '{2}' is read-only" -msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seul" - -#: tiramisu/option.py:159 +#: tiramisu/option.py:68 msgid "invalid name: {0} for option" msgstr "nom invalide : {0} pour l'option" -#: tiramisu/option.py:169 -msgid "validator must be a function" -msgstr "validator doit être une fonction" +#: tiramisu/option.py:77 +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:176 +#: tiramisu/option.py:115 +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 +msgid "information's item not found: {0}" +msgstr "aucune config spécifié alors que c'est nécessaire" + +#: tiramisu/option.py:204 +msgid "cannot serialize Option, only in OptionDescription" +msgstr "ne peut serialiser une Option, seulement via une OptionDescription" + +#: tiramisu/option.py:307 msgid "a default_multi is set whereas multi is False in option: {0}" msgstr "" -"une default_multi est renseigné alors que multi est False dans l'option : {0}" +"une default_multi est renseignée alors que multi est False dans l'option : " +"{0}" -#: tiramisu/option.py:182 +#: tiramisu/option.py:313 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:187 +#: tiramisu/option.py:318 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é" +msgstr "la valeur par défaut n'est pas possible si l'option {0} est calculée" -#: tiramisu/option.py:190 +#: tiramisu/option.py:321 msgid "" "params defined for a callback function but no callback defined yet for " "option {0}" msgstr "" -"params définit pour une fonction callback mais par de callback défini encore " -"pour l'option {0}" +"params définis pour une fonction callback mais par de callback encore " +"définis pour l'option {0}" -#: tiramisu/option.py:212 tiramisu/option.py:753 -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:360 +msgid "option not in all_cons_opts" +msgstr "option non présentante dans all_cons_opts" -#: tiramisu/option.py:285 -msgid "invalid value {0} for option {1} for object {2}" -msgstr "valeur invalide {0} pour l'option {1} pour l'objet {2}" - -#: tiramisu/option.py:293 tiramisu/value.py:468 +#: 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:305 -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" +#: tiramisu/option.py:449 +msgid "which must be a list" +msgstr "lequel doit être une liste" -#: tiramisu/option.py:374 -msgid "invalid value {0} for option {1} must be different as {2} option" +#: tiramisu/option.py:509 +msgid "consistency should be set with an option" +msgstr "consistency doit être configuré avec une option" + +#: tiramisu/option.py:511 +msgid "cannot add consistency with itself" +msgstr "ne peut ajouter une consistency avec lui même" + +#: tiramisu/option.py:513 +msgid "every options in consistency should be multi or none" msgstr "" -"valeur invalide {0} pour l'option {1} doit être différent que l'option {2}" +"toutes les options d'une consistency devrait être multi ou ne pas l'être" -#: tiramisu/option.py:396 +#: tiramisu/option.py:533 +msgid "same value for {0} and {1}" +msgstr "même valeur pour {0} et {1}" + +#: tiramisu/option.py:642 msgid "values must be a tuple for {0}" msgstr "values doit être un tuple pour {0}" -#: tiramisu/option.py:399 +#: tiramisu/option.py:645 msgid "open_values must be a boolean for {0}" msgstr "open_values doit être un booléen pour {0}" -#: tiramisu/option.py:420 +#: tiramisu/option.py:667 msgid "value {0} is not permitted, only {1} is allowed" -msgstr "valeur {0} n'est pas permit, seules {1} sont autorisées" +msgstr "valeur {0} n'est pas permis, seules {1} sont autorisées" -#: tiramisu/option.py:432 +#: tiramisu/option.py:679 msgid "value must be a boolean" msgstr "valeur doit être un booléen" -#: tiramisu/option.py:442 +#: tiramisu/option.py:689 msgid "value must be an integer" -msgstr "valeur doit être un numbre" +msgstr "valeur doit être un nombre entier" -#: tiramisu/option.py:452 +#: tiramisu/option.py:699 msgid "value must be a float" msgstr "valeur doit être un nombre flottant" -#: tiramisu/option.py:462 +#: tiramisu/option.py:709 msgid "value must be a string, not {0}" msgstr "valeur doit être une chaîne, pas {0}" -#: tiramisu/option.py:480 +#: tiramisu/option.py:727 msgid "value must be an unicode" msgstr "valeur doit être une valeur unicode" -#: tiramisu/option.py:490 +#: tiramisu/option.py:739 msgid "malformed symlinkoption must be an option for symlink {0}" -msgstr "symlinkoption mal formé doit être une option pour symlink {0}" +msgstr "symlinkoption mal formé, doit être une option pour symlink {0}" -#: tiramisu/option.py:526 +#: tiramisu/option.py:788 +msgid "invalid IP {0}" +msgstr "adresse IP invalide {0}" + +#: 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:528 +#: tiramisu/option.py:795 msgid "IP must be in private class" msgstr "IP doit être dans la classe privée" -#: tiramisu/option.py:566 +#: tiramisu/option.py:833 msgid "inconsistency in allowed range" msgstr "inconsistence dans la plage autorisée" -#: tiramisu/option.py:571 +#: tiramisu/option.py:838 msgid "max value is empty" -msgstr "valeur maximum est vide" +msgstr "la valeur maximum est vide" -#: tiramisu/option.py:608 -msgid "network mustn't not be in reserved class" -msgstr "réseau ne doit pas être dans la classe reservée" +#: tiramisu/option.py:877 +msgid "invalid network address {0}" +msgstr "adresse réseau invalide {0}" -#: tiramisu/option.py:640 +#: tiramisu/option.py:882 +msgid "network shall not be in reserved class" +msgstr "le réseau ne doit pas être dans la classe reservée" + +#: tiramisu/option.py:894 +msgid "invalid netmask address {0}" +msgstr "masque de sous-réseau invalide {0}" + +#: tiramisu/option.py:910 +msgid "invalid len for opts" +msgstr "longueur invalide pour opts" + +#: tiramisu/option.py:922 msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" msgstr "réseau invalide {0} ({1}) avec masque {2} ({3}), ce réseau est une IP" -#: tiramisu/option.py:645 +#: tiramisu/option.py:927 msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" msgstr "IP invalide {0} ({1}) avec masque {2} ({3}), cette IP est un réseau" -#: tiramisu/option.py:650 +#: tiramisu/option.py:932 msgid "invalid IP {0} ({1}) with netmask {2} ({3})" msgstr "IP invalide {0} ({1}) avec masque {2} ({3})" -#: tiramisu/option.py:652 +#: 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:672 +#: tiramisu/option.py:948 +msgid "invalid broadcast address {0}" +msgstr "adresse de broadcast invalide {0}" + +#: tiramisu/option.py:952 +msgid "invalid len for vals" +msgstr "longueur invalide pour vals" + +#: tiramisu/option.py:957 +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 msgid "unknown type_ {0} for hostname" msgstr "type_ inconnu {0} pour le nom d'hôte" -#: tiramisu/option.py:675 +#: tiramisu/option.py:982 msgid "allow_ip must be a boolean" msgstr "allow_ip doit être un booléen" -#: tiramisu/option.py:704 +#: tiramisu/option.py:1012 msgid "invalid value for {0}, must have dot" msgstr "valeur invalide pour {0}, doit avoir un point" -#: tiramisu/option.py:707 +#: 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:710 +#: 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:714 +#: tiramisu/option.py:1022 msgid "invalid domainname" msgstr "nom de domaine invalide" -#: tiramisu/option.py:731 -msgid "invalid name: {0} for optiondescription" -msgstr "nom invalide : {0} pour l'optiondescription" - -#: tiramisu/option.py:743 +#: tiramisu/option.py:1049 msgid "duplicate option name: {0}" msgstr "nom de l'option dupliqué : {0}" -#: tiramisu/option.py:769 +#: tiramisu/option.py:1067 msgid "unknown Option {0} in OptionDescription {1}" -msgstr "Option {} inconnue pour l'OptionDescription{}" +msgstr "Option {0} inconnue pour l'OptionDescription {1}" -#: tiramisu/option.py:820 +#: tiramisu/option.py:1118 msgid "duplicate option: {0}" msgstr "option dupliquée : {0}" -#: tiramisu/option.py:850 +#: tiramisu/option.py:1148 +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 msgid "no option for path {0}" msgstr "pas d'option pour le chemin {0}" -#: tiramisu/option.py:856 +#: tiramisu/option.py:1162 msgid "no option {0} found" msgstr "pas d'option {0} trouvée" -#: tiramisu/option.py:866 +#: tiramisu/option.py:1172 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:879 +#: tiramisu/option.py:1185 msgid "master group {0} shall not have a subgroup" msgstr "groupe maître {0} ne doit pas avoir de sous-groupe" -#: tiramisu/option.py:882 +#: tiramisu/option.py:1188 msgid "master group {0} shall not have a symlinkoption" msgstr "groupe maître {0} ne doit pas avoir de symlinkoption" -#: tiramisu/option.py:885 +#: tiramisu/option.py:1191 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:896 +#: tiramisu/option.py:1202 msgid "master group with wrong master name for {0}" -msgstr "le groupe maître avec un nom de maître éroné pour {0}" +msgstr "le groupe maître avec un nom de maître érroné pour {0}" -#: tiramisu/option.py:905 +#: 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:908 +#: tiramisu/option.py:1214 msgid "group_type: {0} not allowed" msgstr "group_type : {0} non autorisé" -#: tiramisu/option.py:946 +#: tiramisu/option.py:1306 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:962 +#: tiramisu/option.py:1323 msgid "" "malformed requirements for option: {0} require must have option, expected " "and action keys" msgstr "" "requirements malformé pour l'option : {0} l'exigence doit avoir les clefs " -"option, exptected et action" +"option, expected et action" -#: tiramisu/option.py:967 +#: tiramisu/option.py:1328 msgid "malformed requirements for option: {0} inverse must be boolean" -msgstr "requirements malformé pour l'option : {0} inverse doit être un booléen" +msgstr "" +"requirements mal formés pour l'option : {0} inverse doit être un booléen" -#: tiramisu/option.py:971 +#: tiramisu/option.py:1332 msgid "malformed requirements for option: {0} transitive must be boolean" -msgstr "requirements malformé pour l'option : {0} transitive doit être booléen" +msgstr "" +"requirements mal formés pour l'option : {0} transitive doit être booléen" -#: tiramisu/option.py:975 +#: tiramisu/option.py:1336 msgid "malformed requirements for option: {0} same_action must be boolean" msgstr "" -"requirements malformé pour l'option : {0} same_action doit être un booléen" +"requirements mal formés pour l'option : {0} same_action doit être un booléen" -#: tiramisu/option.py:979 +#: tiramisu/option.py:1340 msgid "malformed requirements must be an option in option {0}" -msgstr "requirements malformé doit être une option dans l'option {0}" +msgstr "requirements mal formés doit être une option dans l'option {0}" -#: tiramisu/option.py:982 +#: tiramisu/option.py:1343 msgid "malformed requirements option {0} should not be a multi" -msgstr "requirements malformé l'option {0} ne doit pas être une multi" +msgstr "requirements mal formés l'option {0} ne doit pas être une multi" -#: tiramisu/option.py:988 +#: tiramisu/option.py:1349 msgid "" "malformed requirements second argument must be valid for option {0}: {1}" msgstr "" -"requirements malformé deuxième argument doit être valide pour l'option {0} : " -"{1}" +"requirements mal formés deuxième argument doit être valide pour l'option " +"{0} : {1}" -#: tiramisu/option.py:993 +#: tiramisu/option.py:1354 msgid "inconsistency in action types for option: {0} action: {1}" msgstr "incohérence dans les types action pour l'option : {0} action {1}" -#: tiramisu/setting.py:47 -msgid "storage_type is already set, cannot rebind it" -msgstr "storage_type est déjà défini, impossible de le redéfinir" +#: tiramisu/option.py:1379 +msgid "{0} should be a function" +msgstr "{0} doit être une fonction" -#: tiramisu/setting.py:67 +#: tiramisu/option.py:1382 +msgid "{0}_params should be a dict" +msgstr "{0}_params devrait être un dict" + +#: tiramisu/option.py:1385 +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 +msgid "{0}_params should be tuple for key \"{1}\"" +msgstr "{0}_params devrait être un tuple pour la clef \"{1}\"" + +#: tiramisu/option.py:1395 +msgid "validator not support tuple" +msgstr "validator n'accepte pas de tuple" + +#: tiramisu/option.py:1398 +msgid "{0}_params should have an option not a {0} for first argument" +msgstr "{0}_params devrait avoir une option pas un {0} pour premier argument" + +#: tiramisu/option.py:1402 +msgid "{0}_params should have a boolean not a {0} for second argument" +msgstr "{0}_params devrait avoir un boolean pas un {0} pour second argument" + +#: tiramisu/setting.py:111 msgid "can't rebind {0}" msgstr "ne peut redéfinir ({0})" -#: tiramisu/setting.py:72 +#: tiramisu/setting.py:116 msgid "can't unbind {0}" msgstr "ne peut supprimer ({0})" -#: tiramisu/setting.py:185 +#: tiramisu/setting.py:254 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:215 -msgid "option {0} not already exists in storage {1}" -msgstr "option {0} n'existe pas dans l'espace de stockage {1}" - -#: tiramisu/setting.py:282 +#: tiramisu/setting.py:317 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:297 +#: tiramisu/setting.py:332 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:391 +#: tiramisu/setting.py:435 msgid "cannot change the value for option {0} this option is frozen" msgstr "" -"ne peut modifié la valeur de l'option {0} cette option n'est pas modifiable" +"ne peut modifier la valeur de l'option {0} cette option n'est pas modifiable" -#: tiramisu/setting.py:397 +#: tiramisu/setting.py:441 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:415 +#: tiramisu/setting.py:459 msgid "permissive must be a tuple" msgstr "permissive doit être un tuple" -#: tiramisu/setting.py:422 tiramisu/value.py:277 +#: tiramisu/setting.py:466 tiramisu/value.py:299 msgid "invalid generic owner {0}" msgstr "invalide owner générique {0}" -#: tiramisu/setting.py:503 +#: tiramisu/setting.py:553 msgid "" "malformed requirements imbrication detected for option: '{0}' with " "requirement on: '{1}'" msgstr "" -"imbrication de requirements malformé detectée pour l'option : '{0}' avec " +"imbrication de requirements mal formés detectée pour l'option : '{0}' avec " "requirement sur : '{1}'" -#: tiramisu/setting.py:515 +#: tiramisu/setting.py:565 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 +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 +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 msgid "dictionary storage cannot delete session" msgstr "" "impossible de supprimer une session dans un espace de stockage dictionary" -#: tiramisu/storage/dictionary/storage.py:46 +#: tiramisu/storage/dictionary/storage.py:48 msgid "session already used" msgstr "session déjà utilisée" -#: tiramisu/storage/dictionary/storage.py:48 +#: tiramisu/storage/dictionary/storage.py:50 msgid "a dictionary cannot be persistent" msgstr "un espace de stockage dictionary ne peut être persistant" -#: tiramisu/value.py:284 +#: tiramisu/value.py:306 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:356 +#: tiramisu/value.py:414 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:373 +#: tiramisu/value.py:438 msgid "invalid len for the master: {0} which has {1} as slave with greater len" msgstr "" "longueur invalide pour un maître : {0} qui a {1} une esclave avec une plus " "grande longueur" -#: tiramisu/value.py:394 +#: tiramisu/value.py:468 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:429 +#: tiramisu/value.py:505 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:433 +#: tiramisu/value.py:509 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:442 +#: tiramisu/value.py:518 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:450 +#: tiramisu/value.py:526 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:458 +#: tiramisu/value.py:534 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:482 +#: tiramisu/value.py:562 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 "metaconfig's children must be a list" -#~ msgstr "enfants d'une metaconfig doit être une liste" +#~ 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 "invalid value {0} for option {1} must be different as {2} option" +#~ msgstr "" +#~ "valeur invalide {0} pour l'option {1} doit être différente de l'option {2}" + +#~ msgid "validator should return a boolean, not {0}" +#~ msgstr "le validator devrait retourner un boolean, pas un {0}" + +#~ msgid "invalid value {0} for option {1} for object {2}" +#~ msgstr "valeur invalide {0} pour l'option {1} pour l'objet {2}" + +#~ msgid "no config specified but needed" +#~ msgstr "aucune config spécifié alors que c'est nécessaire" + +#~ msgid "{0} has no attribute impl_set_information" +#~ msgstr "{0} n'a pas d'attribut impl_set_information" + +#~ msgid "{0} has no attribute impl_get_information" +#~ msgstr "{0} n'a pas d'attribut impl_get_information" + +#~ msgid "invalid name: {0} for optiondescription" +#~ msgstr "nom invalide : {0} pour l'optiondescription" #~ msgid "metaconfig's children must be config, not {0}" #~ msgstr "enfants d'une metaconfig doit être une config, pas {0}" diff --git a/translations/tiramisu.pot b/translations/tiramisu.pot index 3b7b989..57b90e8 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-02 11:30+CEST\n" +"POT-Creation-Date: 2013-09-28 19:06+CEST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,395 +15,455 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: tiramisu/autolib.py:58 -msgid "no config specified but needed" -msgstr "" - -#: tiramisu/autolib.py:65 +#: tiramisu/autolib.py:144 msgid "unable to carry out a calculation, option {0} has properties: {1} for: {2}" msgstr "" -#: tiramisu/autolib.py:74 +#: 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:47 +#: tiramisu/config.py:51 msgid "descr must be an optiondescription, not {0}" msgstr "" -#: tiramisu/config.py:121 +#: tiramisu/config.py:126 msgid "unknown group_type: {0}" msgstr "" -#: tiramisu/config.py:157 +#: tiramisu/config.py:162 msgid "no option description found for this config (may be metaconfig without meta)" msgstr "" -#: tiramisu/config.py:311 +#: tiramisu/config.py:188 +msgid "can't assign to an OptionDescription" +msgstr "" + +#: tiramisu/config.py:319 msgid "unknown type_ type {0}for _find" msgstr "" -#: tiramisu/config.py:350 +#: tiramisu/config.py:358 msgid "no option found in config with these criteria" msgstr "" -#: tiramisu/config.py:400 +#: tiramisu/config.py:408 msgid "make_dict can't filtering with value without option" msgstr "" -#: tiramisu/config.py:421 +#: tiramisu/config.py:429 msgid "unexpected path {0}, should start with {1}" msgstr "" -#: tiramisu/config.py:481 +#: tiramisu/config.py:489 msgid "opt in getowner must be an option not {0}" msgstr "" -#: tiramisu/option.py:71 -msgid "{0} has no attribute impl_set_information" -msgstr "" - -#: tiramisu/option.py:86 -msgid "information's item not found: {0}" -msgstr "" - -#: tiramisu/option.py:89 -msgid "{0} has no attribute impl_get_information" -msgstr "" - -#: tiramisu/option.py:117 -msgid "'{0}' ({1}) object attribute '{2}' is read-only" -msgstr "" - -#: tiramisu/option.py:208 +#: tiramisu/option.py:68 msgid "invalid name: {0} for option" msgstr "" -#: tiramisu/option.py:218 -msgid "validator must be a function" -msgstr "" - -#: tiramisu/option.py:225 -msgid "a default_multi is set whereas multi is False in option: {0}" -msgstr "" - -#: tiramisu/option.py:231 -msgid "invalid default_multi value {0} for option {1}: {2}" -msgstr "" - -#: tiramisu/option.py:236 -msgid "default value not allowed if option: {0} is calculated" -msgstr "" - -#: tiramisu/option.py:239 -msgid "params defined for a callback function but no callback defined yet for option {0}" -msgstr "" - -#: tiramisu/option.py:261 tiramisu/option.py:809 +#: tiramisu/option.py:77 msgid "invalid properties type {0} for {1}, must be a tuple" msgstr "" -#: tiramisu/option.py:334 -msgid "invalid value {0} for option {1} for object {2}" +#: tiramisu/option.py:115 +msgid "'{0}' ({1}) object attribute '{2}' is read-only" msgstr "" -#: tiramisu/option.py:342 tiramisu/value.py:468 +#: tiramisu/option.py:142 tiramisu/value.py:360 +msgid "information's item not found: {0}" +msgstr "" + +#: tiramisu/option.py:204 +msgid "cannot serialize Option, only in OptionDescription" +msgstr "" + +#: tiramisu/option.py:307 +msgid "a default_multi is set whereas multi is False in option: {0}" +msgstr "" + +#: tiramisu/option.py:313 +msgid "invalid default_multi value {0} for option {1}: {2}" +msgstr "" + +#: tiramisu/option.py:318 +msgid "default value not allowed if option: {0} is calculated" +msgstr "" + +#: tiramisu/option.py:321 +msgid "params defined for a callback function but no callback defined yet for option {0}" +msgstr "" + +#: tiramisu/option.py:360 +msgid "option not in all_cons_opts" +msgstr "" + +#: tiramisu/option.py:432 tiramisu/value.py:545 msgid "invalid value {0} for option {1}: {2}" msgstr "" -#: tiramisu/option.py:354 -msgid "invalid value {0} for option {1} which must be a list" +#: tiramisu/option.py:449 +msgid "which must be a list" msgstr "" -#: tiramisu/option.py:423 -msgid "invalid value {0} for option {1} must be different as {2} option" -msgstr "" - -#: tiramisu/option.py:445 -msgid "values must be a tuple for {0}" -msgstr "" - -#: tiramisu/option.py:448 -msgid "open_values must be a boolean for {0}" -msgstr "" - -#: tiramisu/option.py:469 -msgid "value {0} is not permitted, only {1} is allowed" -msgstr "" - -#: tiramisu/option.py:481 -msgid "value must be a boolean" -msgstr "" - -#: tiramisu/option.py:491 -msgid "value must be an integer" -msgstr "" - -#: tiramisu/option.py:501 -msgid "value must be a float" +#: tiramisu/option.py:509 +msgid "consistency should be set with an option" msgstr "" #: tiramisu/option.py:511 +msgid "cannot add consistency with itself" +msgstr "" + +#: tiramisu/option.py:513 +msgid "every options in consistency should be multi or none" +msgstr "" + +#: tiramisu/option.py:533 +msgid "same value for {0} and {1}" +msgstr "" + +#: tiramisu/option.py:642 +msgid "values must be a tuple for {0}" +msgstr "" + +#: tiramisu/option.py:645 +msgid "open_values must be a boolean for {0}" +msgstr "" + +#: tiramisu/option.py:667 +msgid "value {0} is not permitted, only {1} is allowed" +msgstr "" + +#: tiramisu/option.py:679 +msgid "value must be a boolean" +msgstr "" + +#: tiramisu/option.py:689 +msgid "value must be an integer" +msgstr "" + +#: tiramisu/option.py:699 +msgid "value must be a float" +msgstr "" + +#: tiramisu/option.py:709 msgid "value must be a string, not {0}" msgstr "" -#: tiramisu/option.py:529 +#: tiramisu/option.py:727 msgid "value must be an unicode" msgstr "" -#: tiramisu/option.py:539 +#: tiramisu/option.py:739 msgid "malformed symlinkoption must be an option for symlink {0}" msgstr "" -#: tiramisu/option.py:581 +#: tiramisu/option.py:788 +msgid "invalid IP {0}" +msgstr "" + +#: tiramisu/option.py:793 msgid "IP mustn't not be in reserved class" msgstr "" -#: tiramisu/option.py:583 +#: tiramisu/option.py:795 msgid "IP must be in private class" msgstr "" -#: tiramisu/option.py:621 +#: tiramisu/option.py:833 msgid "inconsistency in allowed range" msgstr "" -#: tiramisu/option.py:626 +#: tiramisu/option.py:838 msgid "max value is empty" msgstr "" -#: tiramisu/option.py:663 -msgid "network mustn't not be in reserved class" +#: tiramisu/option.py:877 +msgid "invalid network address {0}" msgstr "" -#: tiramisu/option.py:695 -msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" +#: tiramisu/option.py:882 +msgid "network shall not be in reserved class" msgstr "" -#: tiramisu/option.py:700 -msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" -msgstr "" - -#: tiramisu/option.py:705 -msgid "invalid IP {0} ({1}) with netmask {2} ({3})" -msgstr "" - -#: tiramisu/option.py:707 -msgid "invalid network {0} ({1}) with netmask {2} ({3})" -msgstr "" - -#: tiramisu/option.py:727 -msgid "unknown type_ {0} for hostname" -msgstr "" - -#: tiramisu/option.py:730 -msgid "allow_ip must be a boolean" -msgstr "" - -#: tiramisu/option.py:759 -msgid "invalid value for {0}, must have dot" -msgstr "" - -#: tiramisu/option.py:762 -msgid "invalid domainname's length for {0} (max {1})" -msgstr "" - -#: tiramisu/option.py:765 -msgid "invalid domainname's length for {0} (min 2)" -msgstr "" - -#: tiramisu/option.py:769 -msgid "invalid domainname" -msgstr "" - -#: tiramisu/option.py:787 -msgid "invalid name: {0} for optiondescription" -msgstr "" - -#: tiramisu/option.py:799 -msgid "duplicate option name: {0}" -msgstr "" - -#: tiramisu/option.py:825 -msgid "unknown Option {0} in OptionDescription {1}" -msgstr "" - -#: tiramisu/option.py:874 -msgid "duplicate option: {0}" -msgstr "" - -#: tiramisu/option.py:904 -msgid "no option for path {0}" +#: tiramisu/option.py:894 +msgid "invalid netmask address {0}" msgstr "" #: tiramisu/option.py:910 +msgid "invalid len for opts" +msgstr "" + +#: tiramisu/option.py:922 +msgid "invalid network {0} ({1}) with netmask {2} ({3}), this network is an IP" +msgstr "" + +#: tiramisu/option.py:927 +msgid "invalid IP {0} ({1}) with netmask {2} ({3}), this IP is a network" +msgstr "" + +#: tiramisu/option.py:932 +msgid "invalid IP {0} ({1}) with netmask {2} ({3})" +msgstr "" + +#: tiramisu/option.py:934 +msgid "invalid network {0} ({1}) with netmask {2} ({3})" +msgstr "" + +#: tiramisu/option.py:948 +msgid "invalid broadcast address {0}" +msgstr "" + +#: tiramisu/option.py:952 +msgid "invalid len for vals" +msgstr "" + +#: tiramisu/option.py:957 +msgid "invalid broadcast {0} ({1}) with network {2} ({3}) and netmask {4} ({5})" +msgstr "" + +#: tiramisu/option.py:979 +msgid "unknown type_ {0} for hostname" +msgstr "" + +#: tiramisu/option.py:982 +msgid "allow_ip must be a boolean" +msgstr "" + +#: tiramisu/option.py:1012 +msgid "invalid value for {0}, must have dot" +msgstr "" + +#: tiramisu/option.py:1015 +msgid "invalid domainname's length for {0} (max {1})" +msgstr "" + +#: tiramisu/option.py:1018 +msgid "invalid domainname's length for {0} (min 2)" +msgstr "" + +#: tiramisu/option.py:1022 +msgid "invalid domainname" +msgstr "" + +#: tiramisu/option.py:1049 +msgid "duplicate option name: {0}" +msgstr "" + +#: tiramisu/option.py:1067 +msgid "unknown Option {0} in OptionDescription {1}" +msgstr "" + +#: tiramisu/option.py:1118 +msgid "duplicate option: {0}" +msgstr "" + +#: tiramisu/option.py:1148 +msgid "consistency with option {0} which is not in Config" +msgstr "" + +#: tiramisu/option.py:1156 +msgid "no option for path {0}" +msgstr "" + +#: tiramisu/option.py:1162 msgid "no option {0} found" msgstr "" -#: tiramisu/option.py:920 +#: tiramisu/option.py:1172 msgid "cannot change group_type if already set (old {0}, new {1})" msgstr "" -#: tiramisu/option.py:933 +#: tiramisu/option.py:1185 msgid "master group {0} shall not have a subgroup" msgstr "" -#: tiramisu/option.py:936 +#: tiramisu/option.py:1188 msgid "master group {0} shall not have a symlinkoption" msgstr "" -#: tiramisu/option.py:939 +#: tiramisu/option.py:1191 msgid "not allowed option {0} in group {1}: this option is not a multi" msgstr "" -#: tiramisu/option.py:950 +#: tiramisu/option.py:1202 msgid "master group with wrong master name for {0}" msgstr "" -#: tiramisu/option.py:959 +#: tiramisu/option.py:1211 msgid "no child has same nom has master group for: {0}" msgstr "" -#: tiramisu/option.py:962 +#: tiramisu/option.py:1214 msgid "group_type: {0} not allowed" msgstr "" -#: tiramisu/option.py:1021 +#: tiramisu/option.py:1306 msgid "malformed requirements type for option: {0}, must be a dict" msgstr "" -#: tiramisu/option.py:1037 +#: tiramisu/option.py:1323 msgid "malformed requirements for option: {0} require must have option, expected and action keys" msgstr "" -#: tiramisu/option.py:1042 +#: tiramisu/option.py:1328 msgid "malformed requirements for option: {0} inverse must be boolean" msgstr "" -#: tiramisu/option.py:1046 +#: tiramisu/option.py:1332 msgid "malformed requirements for option: {0} transitive must be boolean" msgstr "" -#: tiramisu/option.py:1050 +#: tiramisu/option.py:1336 msgid "malformed requirements for option: {0} same_action must be boolean" msgstr "" -#: tiramisu/option.py:1054 +#: tiramisu/option.py:1340 msgid "malformed requirements must be an option in option {0}" msgstr "" -#: tiramisu/option.py:1057 +#: tiramisu/option.py:1343 msgid "malformed requirements option {0} should not be a multi" msgstr "" -#: tiramisu/option.py:1063 +#: tiramisu/option.py:1349 msgid "malformed requirements second argument must be valid for option {0}: {1}" msgstr "" -#: tiramisu/option.py:1068 +#: tiramisu/option.py:1354 msgid "inconsistency in action types for option: {0} action: {1}" msgstr "" -#: tiramisu/setting.py:47 -msgid "storage_type is already set, cannot rebind it" +#: tiramisu/option.py:1379 +msgid "{0} should be a function" msgstr "" -#: tiramisu/setting.py:67 +#: tiramisu/option.py:1382 +msgid "{0}_params should be a dict" +msgstr "" + +#: tiramisu/option.py:1385 +msgid "{0}_params with key {1} should not have length different to 1" +msgstr "" + +#: tiramisu/option.py:1389 +msgid "{0}_params should be tuple for key \"{1}\"" +msgstr "" + +#: tiramisu/option.py:1395 +msgid "validator not support tuple" +msgstr "" + +#: tiramisu/option.py:1398 +msgid "{0}_params should have an option not a {0} for first argument" +msgstr "" + +#: tiramisu/option.py:1402 +msgid "{0}_params should have a boolean not a {0} for second argument" +msgstr "" + +#: tiramisu/setting.py:111 msgid "can't rebind {0}" msgstr "" -#: tiramisu/setting.py:72 +#: tiramisu/setting.py:116 msgid "can't unbind {0}" msgstr "" -#: tiramisu/setting.py:185 +#: tiramisu/setting.py:254 msgid "cannot append {0} property for option {1}: this property is calculated" msgstr "" -#: tiramisu/setting.py:215 -msgid "option {0} not already exists in storage {1}" -msgstr "" - -#: tiramisu/setting.py:282 +#: tiramisu/setting.py:317 msgid "opt and all_properties must not be set together in reset" msgstr "" -#: tiramisu/setting.py:297 +#: tiramisu/setting.py:332 msgid "if opt is not None, path should not be None in _getproperties" msgstr "" -#: tiramisu/setting.py:391 +#: tiramisu/setting.py:435 msgid "cannot change the value for option {0} this option is frozen" msgstr "" -#: tiramisu/setting.py:397 +#: tiramisu/setting.py:441 msgid "trying to access to an option named: {0} with properties {1}" msgstr "" -#: tiramisu/setting.py:415 +#: tiramisu/setting.py:459 msgid "permissive must be a tuple" msgstr "" -#: tiramisu/setting.py:422 tiramisu/value.py:277 +#: tiramisu/setting.py:466 tiramisu/value.py:299 msgid "invalid generic owner {0}" msgstr "" -#: tiramisu/setting.py:503 +#: tiramisu/setting.py:553 msgid "malformed requirements imbrication detected for option: '{0}' with requirement on: '{1}'" msgstr "" -#: tiramisu/setting.py:515 +#: tiramisu/setting.py:565 msgid "option '{0}' has requirement's property error: {1} {2}" msgstr "" +#: tiramisu/storage/__init__.py:47 +msgid "storage_type is already set, cannot rebind it" +msgstr "" + +#: tiramisu/storage/__init__.py:81 +msgid "option {0} not already exists in storage {1}" +msgstr "" + #: tiramisu/storage/dictionary/storage.py:37 msgid "dictionary storage cannot delete session" msgstr "" -#: tiramisu/storage/dictionary/storage.py:46 +#: tiramisu/storage/dictionary/storage.py:48 msgid "session already used" msgstr "" -#: tiramisu/storage/dictionary/storage.py:48 +#: tiramisu/storage/dictionary/storage.py:50 msgid "a dictionary cannot be persistent" msgstr "" -#: tiramisu/value.py:284 +#: tiramisu/value.py:306 msgid "no value for {0} cannot change owner to {1}" msgstr "" -#: tiramisu/value.py:356 +#: tiramisu/value.py:414 msgid "invalid len for the slave: {0} which has {1} as master" msgstr "" -#: tiramisu/value.py:373 +#: tiramisu/value.py:438 msgid "invalid len for the master: {0} which has {1} as slave with greater len" msgstr "" -#: tiramisu/value.py:394 +#: tiramisu/value.py:468 msgid "cannot append a value on a multi option {0} which is a slave" msgstr "" -#: tiramisu/value.py:429 +#: tiramisu/value.py:505 msgid "cannot sort multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:433 +#: tiramisu/value.py:509 msgid "cmp is not permitted in python v3 or greater" msgstr "" -#: tiramisu/value.py:442 +#: tiramisu/value.py:518 msgid "cannot reverse multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:450 +#: tiramisu/value.py:526 msgid "cannot insert multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:458 +#: tiramisu/value.py:534 msgid "cannot extend multi option {0} if master or slave" msgstr "" -#: tiramisu/value.py:482 +#: tiramisu/value.py:562 msgid "cannot pop a value on a multi option {0} which is a slave" msgstr ""