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 @@
+
+
+
+
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 ""