diff --git a/ChangeLog b/ChangeLog index 6c8f3ca..070a857 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +Sun Nov 29 23:01:28 2015 +0200 Emmanuel Garette + * requires could be apply to a slave and properties could be different + Mon Oct 12 17:05:28 2015 +0200 Emmanuel Garette * domainname with only one character is now allowed diff --git a/test/test_cache.py b/test/test_cache.py index 7a6f6dc..cb0cb03 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -4,9 +4,10 @@ do_autopath() from tiramisu import setting setting.expires_time = 1 -from tiramisu.option import IntOption, OptionDescription +from tiramisu.option import IntOption, StrOption, OptionDescription from tiramisu.config import Config -from tiramisu.error import ConfigError, PropertiesOptionError +from tiramisu.error import ConfigError +from tiramisu.setting import groups from time import sleep, time @@ -50,9 +51,9 @@ def test_get_cache(): values = c.cfgimpl_get_values() settings = c.cfgimpl_get_settings() ntime = time() + 1 - settings._p_.setcache('u1', set(['inject']), ntime) + settings._p_.setcache('u1', set(['inject']), ntime, None) assert 'inject' in settings[od1.u1] - values._p_.setcache('u1', 100, ntime) + values._p_.setcache('u1', 100, ntime, None) assert c.u1 == [100] @@ -62,9 +63,9 @@ def test_get_cache_no_expire(): c = Config(od1) values = c.cfgimpl_get_values() settings = c.cfgimpl_get_settings() - settings._p_.setcache('u1', set(['inject2']), None) + settings._p_.setcache('u1', set(['inject2']), None, None) assert 'inject2' in settings[od1.u1] - values._p_.setcache('u1', 200, None) + values._p_.setcache('u1', 200, None, None) assert c.u1 == [200] @@ -279,13 +280,70 @@ def test_force_cache(): c.cfgimpl_get_settings().remove('expire') c.cfgimpl_get_values().force_cache() - assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None), 'u4': (None, None)} - assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)} + assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': {None: ([], None)}, + 'u2': {None: (None, None)}, + 'u3': {None: ([], None)}, + 'u4': {None: (None, None)}} + assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u1': {None: (set([]), None)}, + 'u2': {None: (set([]), None)}, + 'u3': {None: (set([]), None)}, + 'u4': {None: (set(['disabled']), None)}} c.read_only() c.cfgimpl_get_values().force_cache() - assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': ([], None), 'u3': ([], None), 'u2': (None, None)} - assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u4': (set(['disabled']), None), 'u1': (set([]), None), 'u3': (set([]), None), 'u2': (set([]), None)} + assert c.cfgimpl_get_values()._p_.get_cached(c) == {'u1': {None: ([], None)}, + 'u2': {None: (None, None)}, + 'u3': {None: ([], None)}} + assert c.cfgimpl_get_settings()._p_.get_cached(c) == {'u1': {None: (set([]), None)}, + 'u2': {None: (set([]), None)}, + 'u3': {None: (set([]), None)}, + 'u4': {None: (set(['disabled']), None)}} c.cfgimpl_get_settings().remove('cache') raises(ConfigError, "c.cfgimpl_get_values().force_cache()") + + +def test_cache_master_slave(): + 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() + assert cfg.cfgimpl_get_values()._p_.get_cached(cfg) == {} + assert cfg.cfgimpl_get_settings()._p_.get_cached(cfg) == {} + # + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.2') + cfg.ip_admin_eth0.ip_admin_eth0 + cfg.ip_admin_eth0.netmask_admin_eth0 + cache = cfg.cfgimpl_get_values()._p_.get_cached(cfg) + assert set(cache.keys()) == set(['ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) + assert cache['ip_admin_eth0.ip_admin_eth0'][None][0] == ['192.168.1.2'] + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([None, 0]) + assert cache['ip_admin_eth0.netmask_admin_eth0'][None][0] == [None] + assert cache['ip_admin_eth0.netmask_admin_eth0'][0][0] is None + cache = cfg.cfgimpl_get_settings()._p_.get_cached(cfg) + assert set(cache.keys()) == set(['ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert set(cache['ip_admin_eth0'].keys()) == set([None]) + assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([None, 0]) + # + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + cfg.ip_admin_eth0.ip_admin_eth0 + cfg.ip_admin_eth0.netmask_admin_eth0 + cache = cfg.cfgimpl_get_values()._p_.get_cached(cfg) + assert set(cache.keys()) == set(['ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) + assert cache['ip_admin_eth0.ip_admin_eth0'][None][0] == ['192.168.1.2', '192.168.1.1'] + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([None, 0, 1]) + assert cache['ip_admin_eth0.netmask_admin_eth0'][None][0] == [None, None] + assert cache['ip_admin_eth0.netmask_admin_eth0'][0][0] is None + assert cache['ip_admin_eth0.netmask_admin_eth0'][1][0] is None + cache = cfg.cfgimpl_get_settings()._p_.get_cached(cfg) + assert set(cache.keys()) == set(['ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert set(cache['ip_admin_eth0'].keys()) == set([None]) + assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([None, 0, 1]) + #DEL, insert, ... diff --git a/test/test_config_api.py b/test/test_config_api.py index 433d887..4b7bb4d 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -102,6 +102,24 @@ def test_make_dict_with_disabled(): OptionDescription("s1", "", [ BoolOption("a", "", default=False), BoolOption("b", "", default=False, properties=('disabled',))]), + OptionDescription("s2", "", [ + BoolOption("a", "", default=False), + BoolOption("b", "", default=False)], properties=('disabled',)), + IntOption("int", "", default=42)]) + config = Config(descr) + config.read_only() + d = config.make_dict() + assert d == {"s1.a": False, "int": 42} + + +def test_make_dict_with_disabled_in_callback(): + descr = OptionDescription("opt", "", [ + OptionDescription("s1", "", [ + BoolOption("a", "", default=False), + BoolOption("b", "", default=False, properties=('disabled',))]), + OptionDescription("s2", "", [ + BoolOption("a", "", default=False), + BoolOption("b", "", default=False)], properties=('disabled',)), IntOption("int", "", default=42)]) config = Config(descr) config.read_only() diff --git a/test/test_option_setting.py b/test/test_option_setting.py index b1c1e85..83009e1 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -4,7 +4,7 @@ do_autopath() from py.test import raises -from tiramisu.setting import owners +from tiramisu.setting import owners, groups from tiramisu.config import Config from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ StrOption, OptionDescription @@ -96,6 +96,7 @@ def test_default_with_multi(): def test_idontexist(): descr = make_description() cfg = Config(descr) + cfg raises(AttributeError, "cfg.idontexist") @@ -222,9 +223,50 @@ def test_multi_with_requires_with_disabled_in_another_group(): def test_multi_with_requires_that_is_multi(): - s = StrOption("string", "", default=["string"], multi=True) - intoption = IntOption('int', 'Test int option', default=[0], multi=True) - raises(ValueError, "StrOption('str', 'Test string option', default=['abc'], requires=[{'option': intoption, 'expected': 1, 'action': 'hidden'}], multi=True)") + b = IntOption('int', 'Test int option', default=[0], multi=True) + c = StrOption('str', 'Test string option', default=['abc'], requires=[{'option': b, 'expected': 1, 'action': 'hidden'}], multi=True) + descr = OptionDescription("opt", "", [b, c]) + descr + raises(ValueError, "Config(descr)") + + +def test_multi_with_requires_that_is_masterslave(): + b = IntOption('int', 'Test int option', default=[0], multi=True) + c = StrOption('str', 'Test string option', requires=[{'option': b, 'expected': 1, 'action': 'hidden'}], multi=True) + descr = OptionDescription("int", "", [b, c]) + descr.impl_set_group_type(groups.master) + Config(descr) + + +def test_multi_with_requires_that_is_masterslave_master(): + b = IntOption('int', 'Test int option', multi=True) + c = StrOption('str', 'Test string option', requires=[{'option': b, 'expected': 1, 'action': 'hidden'}], multi=True) + descr = OptionDescription("str", "", [c, b]) + descr.impl_set_group_type(groups.master) + raises(ValueError, "Config(descr)") + + +def test_multi_with_requires_that_is_masterslave2(): + b = IntOption('int', 'Test int option', default=[0], multi=True) + c = StrOption('str', 'Test string option', multi=True) + d = StrOption('str1', 'Test string option', requires=[{'option': c, 'expected': '1', 'action': 'hidden'}], multi=True) + descr = OptionDescription("int", "", [b, c, d]) + descr.impl_set_group_type(groups.master) + Config(descr) + + +def test_multi_with_requires_that_is_not_same_masterslave(): + b = IntOption('int', 'Test int option', default=[0], multi=True) + c = StrOption('str', 'Test string option', requires=[{'option': b, 'expected': 1, 'action': 'hidden'}], multi=True) + descr1 = OptionDescription("int", "", [b, c]) + descr1.impl_set_group_type(groups.master) + d = IntOption('int1', 'Test int option', default=[0], multi=True) + e = StrOption('str', 'Test string option', requires=[{'option': b, 'expected': 1, 'action': 'hidden'}], multi=True) + descr2 = OptionDescription("int1", "", [d, e]) + descr2.impl_set_group_type(groups.master) + descr3 = OptionDescription('val', '', [descr1, descr2]) + descr3 + raises(ValueError, "Config(descr3)") def test_multi_with_bool(): @@ -241,6 +283,7 @@ def test_multi_with_bool_two(): s = BoolOption("bool", "", default=[False], multi=True) descr = OptionDescription("options", "", [s]) config = Config(descr) + config assert descr.bool.impl_is_multi() is True raises(ValueError, "config.bool = True") @@ -383,5 +426,6 @@ def test_properties_cached(): c.read_write() setting = c.cfgimpl_get_settings() option = c.cfgimpl_get_description().sub.b1 + option c._setattr('sub.b1', True, force_permissive=True) assert str(setting[b1]) in ["['test']", "[u'test']"] diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index ca65579..ba9a667 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -242,6 +242,7 @@ def test_allowed_groups(): 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 raises(ValueError, "interface1.impl_set_group_type('toto')") @@ -266,6 +267,7 @@ def test_master_not_valid_name(): 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) invalid_group = OptionDescription('interface1', '', [ip_admin_eth0, netmask_admin_eth0]) + invalid_group raises(ValueError, "invalid_group.impl_set_group_type(groups.master)") @@ -274,6 +276,7 @@ def test_sub_group_in_master_group(): netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) subgroup = OptionDescription("subgroup", '', []) invalid_group = OptionDescription('ip_admin_eth0', '', [subgroup, ip_admin_eth0, netmask_admin_eth0]) + invalid_group raises(ValueError, "invalid_group.impl_set_group_type(groups.master)") @@ -281,6 +284,7 @@ def test_group_always_has_multis(): 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") group = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + group raises(ValueError, "group.impl_set_group_type(groups.master)") diff --git a/test/test_requires.py b/test/test_requires.py index 02d1d36..3def709 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -3,6 +3,7 @@ from autopath import do_autopath do_autopath() from copy import copy +from tiramisu.setting import groups from tiramisu import setting setting.expires_time = 1 from tiramisu.option import IPOption, OptionDescription, BoolOption, IntOption, StrOption @@ -52,6 +53,7 @@ def test_requires_with_requires(): def test_requires_invalid(): a = BoolOption('activate_service', '', True) + a raises(ValueError, "IPOption('ip_address_service', '', requires='string')") raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False, 'action': 'disabled', 'unknown': True}])") raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': False}])") @@ -568,6 +570,7 @@ def test_requires_requirement_append(): def test_requires_different_inverse(): a = BoolOption('activate_service', '', True) + a raises(ValueError, "IPOption('ip_address_service', '', requires=[{'option': a, 'expected': True, 'action': 'disabled', 'inverse': True}, {'option': a, 'expected': True, 'action': 'disabled', 'inverse': False}])") @@ -605,5 +608,25 @@ def test_set_item(): def test_properties_conflict(): a = BoolOption('activate_service', '', True) + a raises(ValueError, "IPOption('ip_address_service', '', properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])") raises(ValueError, "od1 = OptionDescription('service', '', [a], properties=('disabled',), requires=[{'option': a, 'expected': False, 'action': 'disabled'}])") + + +def test_master_slave_requires(): + 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, + requires=[{'option': ip_admin_eth0, 'expected': '192.168.1.1', 'action': 'disabled'}]) + 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() + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + assert cfg.ip_admin_eth0.ip_admin_eth0 == [] + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.2') + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [None] + assert cfg.ip_admin_eth0.ip_admin_eth0 == ['192.168.1.2'] + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + assert cfg.ip_admin_eth0.netmask_admin_eth0[0] is None + raises(PropertiesOptionError, "cfg.ip_admin_eth0.netmask_admin_eth0[1]") diff --git a/test/test_slots.py b/test/test_slots.py index d3bfdd0..6412c81 100644 --- a/test/test_slots.py +++ b/test/test_slots.py @@ -62,21 +62,21 @@ def test_slots_option_readonly(): p = URLOption('p', '') q = FilenameOption('q', '') m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l, o, p, q]) - a._requires = 'a' - b._requires = 'b' - c._requires = 'c' - d._requires = 'd' - e._requires = 'e' - g._requires = 'g' - h._requires = 'h' - i._requires = 'i' - j._requires = 'j' - k._requires = 'k' - l._requires = 'l' - m._requires = 'm' - o._requires = 'o' - p._requires = 'p' - q._requires = 'q' + a._requires = (((a,),),) + b._requires = (((a,),),) + c._requires = (((a,),),) + d._requires = (((a,),),) + e._requires = (((a,),),) + g._requires = (((a,),),) + h._requires = (((a,),),) + i._requires = (((a,),),) + j._requires = (((a,),),) + k._requires = (((a,),),) + l._requires = (((a,),),) + m._requires = (((a,),),) + o._requires = (((a,),),) + p._requires = (((a,),),) + q._requires = (((a,),),) Config(m) raises(AttributeError, "a._requires = 'a'") raises(AttributeError, "b._requires = 'b'") diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index acb342b..7e59cce 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -18,10 +18,10 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ "enables us to carry out a calculation and return an option's value" -from tiramisu.error import PropertiesOptionError, ConfigError, ContextError, \ +from .error import PropertiesOptionError, ConfigError, ContextError, \ SlaveError -from tiramisu.i18n import _ -from tiramisu.setting import undefined +from .i18n import _ +from .setting import undefined # ____________________________________________________________ @@ -222,9 +222,13 @@ def carry_out_calculation(option, context, callback, callback_params, else: kwargs[key] = couple[0] ret = calculate(callback, args, kwargs) - if callback_params != {} and isinstance(ret, list) and index is not undefined: - raise SlaveError(_("callback cannot return a list for a " - "slave option ({0})").format(path)) + try: + if callback_params != {} and isinstance(ret, list) and \ + option.impl_is_master_slaves('slave'): + raise SlaveError(_("callback cannot return a list for a " + "slave option ({0})").format(path)) + except AttributeError: + pass return ret diff --git a/tiramisu/config.py b/tiramisu/config.py index 8e3eb47..ac1b6f5 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -22,15 +22,15 @@ import weakref -from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError -from tiramisu.option import OptionDescription, Option, SymLinkOption, \ +from .error import PropertiesOptionError, ConfigError, ConflictError +from .option import OptionDescription, Option, SymLinkOption, \ DynSymLinkOption -from tiramisu.option.baseoption import valid_name -from tiramisu.setting import groups, Settings, default_encoding, undefined -from tiramisu.storage import get_storages, get_storage, set_storage, \ +from .option.baseoption import valid_name +from .setting import groups, Settings, default_encoding, undefined +from .storage import get_storages, get_storage, set_storage, \ _impl_getstate_setting, get_storages_validation -from tiramisu.value import Values, Multi -from tiramisu.i18n import _ +from .value import Values, Multi +from .i18n import _ class SubConfig(object): @@ -242,7 +242,7 @@ class SubConfig(object): return subpath def getattr(self, name, force_permissive=False, validate=True, - _setting_properties=undefined): + _setting_properties=undefined, index=None): """ attribute notation mechanism for accessing the value of an option :param name: attribute name @@ -256,23 +256,25 @@ class SubConfig(object): name, force_permissive=force_permissive) return homeconfig.getattr(name, force_permissive=force_permissive, validate=validate, - _setting_properties=_setting_properties) + _setting_properties=_setting_properties, + index=index) context = self._cfgimpl_get_context() option = self.cfgimpl_get_description().__getattr__(name, context=context) subpath = self._get_subpath(name) if isinstance(option, DynSymLinkOption): - return self.cfgimpl_get_values()._get_cached_item( + return self.cfgimpl_get_values()._get_cached_value( option, path=subpath, validate=validate, force_permissive=force_permissive, - setting_properties=_setting_properties) + setting_properties=_setting_properties, index=index) elif isinstance(option, SymLinkOption): # pragma: no dynoptiondescription cover path = context.cfgimpl_get_description().impl_get_path_by_opt( option._impl_getopt()) return context.getattr(path, validate=validate, force_permissive=force_permissive, - _setting_properties=_setting_properties) + _setting_properties=_setting_properties, + index=index) elif option.impl_is_optiondescription(): self.cfgimpl_get_settings().validate_properties( option, True, False, path=subpath, @@ -280,11 +282,12 @@ class SubConfig(object): setting_properties=_setting_properties) return SubConfig(option, self._impl_context, subpath) else: - return self.cfgimpl_get_values()._get_cached_item( + return self.cfgimpl_get_values()._get_cached_value( option, path=subpath, validate=validate, force_permissive=force_permissive, - setting_properties=_setting_properties) + setting_properties=_setting_properties, + index=index) def find(self, bytype=None, byname=None, byvalue=undefined, type_='option', check_properties=True, force_permissive=False): diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 3373106..b33f5d6 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -22,12 +22,12 @@ import re from types import FunctionType import warnings -from tiramisu.i18n import _ -from tiramisu.setting import log, undefined -from tiramisu.autolib import carry_out_calculation -from tiramisu.error import ConfigError, ValueWarning, PropertiesOptionError,\ +from ..i18n import _ +from ..setting import log, undefined +from ..autolib import carry_out_calculation +from ..error import ConfigError, ValueWarning, PropertiesOptionError,\ ContextError -from tiramisu.storage import get_storages_option +from ..storage import get_storages_option StorageBase = get_storages_option('base') @@ -853,10 +853,6 @@ def validate_requires_arg(requires, name): if not isinstance(option, Option): # pragma: optional cover raise ValueError(_('malformed requirements ' 'must be an option in option {0}').format(name)) - if option.impl_is_multi(): # pragma: optional cover - raise ValueError(_('malformed requirements option {0} ' - 'must not be a multi for {1}').format( - option.impl_getname(), name)) if expected is not None: try: option._validate(expected) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index eaf8579..cc58366 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -19,9 +19,9 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from tiramisu.i18n import _ -from tiramisu.setting import log, undefined -from tiramisu.error import SlaveError, ConfigError +from ..i18n import _ +from ..setting import log, undefined +from ..error import SlaveError, ConfigError, PropertiesOptionError from .baseoption import DynSymLinkOption, SymLinkOption, Option @@ -50,8 +50,8 @@ class MasterSlaves(object): else: if child.impl_getdefault() != []: raise ValueError(_("not allowed default value for option {0} " - "in group {1}").format(child.impl_getname(), - name)) + "in group {1}").format(child.impl_getname(), + name)) slaves.append(child) if self.master is None: # pragma: optional cover raise ValueError(_('master group with wrong' @@ -112,50 +112,41 @@ class MasterSlaves(object): for slave in self.getslaves(opt): if not values.is_default_owner(slave, validate_properties=False, validate_meta=False, index=index): - values._get_cached_item(slave, validate=False, - validate_properties=False - ).pop(index, force=True) + values._get_cached_value(slave, validate=False, + validate_properties=False + ).pop(index, force=True) pass def getitem(self, values, opt, path, validate, force_permissive, force_properties, validate_properties, slave_path=undefined, - slave_value=undefined, setting_properties=undefined, self_properties=undefined): + slave_value=undefined, setting_properties=undefined, + self_properties=undefined, index=None): if self.is_master(opt): return self._getmaster(values, opt, path, validate, force_permissive, force_properties, validate_properties, slave_path, - slave_value, self_properties) + slave_value, self_properties, index) else: return self._getslave(values, opt, path, validate, force_permissive, force_properties, - validate_properties, setting_properties, self_properties) + validate_properties, setting_properties, + self_properties, index) def _getmaster(self, values, opt, path, validate, force_permissive, force_properties, validate_properties, c_slave_path, - c_slave_value, self_properties): + c_slave_value, self_properties, index): value = values._get_validated_value(opt, path, validate, force_permissive, force_properties, validate_properties, - self_properties=self_properties) - if validate is True: + self_properties=self_properties, + index=index) + if index is None and validate is True: masterlen = len(value) for slave in self.getslaves(opt): try: slave_path = slave.impl_getpath(values._getcontext()) slavelen = values._p_.get_max_length(slave_path) - #if c_slave_path == slave_path: - # slave_value = c_slave_value - #else: - # slave_value = values._get_validated_value(slave, - # slave_path, - # False, - # False, - # None, False, - # None, - # self_properties=self_properties, - # masterlen=masterlen) - #slavelen = len(slave_value) self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt) except ConfigError: # pragma: optional cover pass @@ -163,7 +154,7 @@ class MasterSlaves(object): def _getslave(self, values, opt, path, validate, force_permissive, force_properties, validate_properties, setting_properties, - self_properties): + self_properties, index): """ if master has length 0: return [] @@ -191,46 +182,43 @@ class MasterSlaves(object): masterlen = self.get_length(values, opt, validate, undefined, undefined, force_permissive, master=master) - master_is_meta = values._is_meta(opt, masterp) - #value = values._get_validated_value(opt, path, validate, - # force_permissive, - # force_properties, - # validate_properties, - # None, # not undefined - # with_meta=master_is_meta, - # self_properties=self_properties) - #if slave, had values until master's one - #path = opt.impl_getpath(context) - #valuelen = len(value) - #if validate: - # self.validate_slave_length(masterlen, valuelen, - # opt.impl_getname(), opt) - #if valuelen < masterlen: - - #FIXME voir si pas de plus grande valeur ! - value = values._get_multi(opt, path) - for index in range(0, masterlen): - #index = valuelen + num - value.append(values._get_validated_value(opt, path, validate, + master_is_meta = values._is_meta(master, masterp) + multi = values._get_multi(opt, path) + if masterlen == 0: + if validate_properties: + context.cfgimpl_get_settings().validate_properties(opt, False, + False, + value=multi, + path=path, + force_permissive=force_permissive, + force_properties=force_properties, + setting_properties=setting_properties) + else: + one_has_value = False + if index is None: + indexes = range(0, masterlen) + else: + indexes = [index] + for idx in indexes: + try: + value = values._get_cached_value(opt, path, validate, force_permissive, force_properties, validate_properties, with_meta=master_is_meta, - index=index, - self_properties=self_properties, - masterlen=masterlen), - setitem=False, - force=True, - validate=validate) - #FIXME hu? - if validate_properties: - context.cfgimpl_get_settings().validate_properties(opt, False, - False, - value=value, - path=path, - force_permissive=force_permissive, - force_properties=force_properties, - setting_properties=setting_properties) - return value + index=idx, + # not self_properties, + # depends to index + #self_properties=self_properties, + masterlen=masterlen, + from_masterslave=True) + multi.append(value, setitem=False, force=True, validate=validate) + one_has_value = True + except PropertiesOptionError, err: + multi.append_properties_error(err) + if not one_has_value: + #raise last err + raise err + return multi def setitem(self, values, opt, value, path): if self.is_master(opt): @@ -241,15 +229,6 @@ class MasterSlaves(object): slave_path = base_path + slave.impl_getname() slavelen = values._p_.get_max_length(slave_path) self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt) - #slave_value = values._get_validated_value(slave, - # slave_path, - # False, - # False, - # None, False, - # None, - # masterlen=masterlen) # not undefined - #slavelen = len(slave_value) - #self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt) else: self.validate_slave_length(self.get_length(values, opt, slave_path=path), len(value), diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index 7b669aa..3e59e20 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -23,12 +23,12 @@ import re import sys from IPy import IP from types import FunctionType -from tiramisu.setting import log, undefined +from ..setting import log, undefined -from tiramisu.error import ConfigError, ContextError -from tiramisu.i18n import _ +from ..error import ConfigError, ContextError +from ..i18n import _ from .baseoption import Option, validate_callback -from tiramisu.autolib import carry_out_calculation +from ..autolib import carry_out_calculation class ChoiceOption(Option): diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 05ef6ef..46f1074 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -22,13 +22,13 @@ from copy import copy import re -from tiramisu.i18n import _ -from tiramisu.setting import groups, undefined # , log +from ..i18n import _ +from ..setting import groups, undefined # , log from .baseoption import BaseOption, SymLinkOption, allowed_character from . import MasterSlaves -from tiramisu.error import ConfigError, ConflictError -from tiramisu.storage import get_storages_option -from tiramisu.autolib import carry_out_calculation +from ..error import ConfigError, ConflictError +from ..storage import get_storages_option +from ..autolib import carry_out_calculation StorageOptionDescription = get_storages_option('optiondescription') @@ -135,6 +135,19 @@ class OptionDescription(BaseOption, StorageOptionDescription): option._set_readonly() if isinstance(option, OptionDescription): option.impl_validate_options(cache_option) + if option.impl_getrequires() != []: + for requires in option.impl_getrequires(): + for require in requires: + if require[0].impl_is_multi(): + if option.impl_is_master_slaves('slave') and require[0].impl_is_master_slaves(): + if option.impl_get_master_slaves() != require[0].impl_get_master_slaves(): + raise ValueError(_('malformed requirements option {0} ' + 'must be in same master/slaves for {1}').format( + require[0].impl_getname(), option.impl_getname())) + else: + raise ValueError(_('malformed requirements option {0} ' + 'must not be a multi for {1}').format( + require[0].impl_getname(), option.impl_getname())) if init: if len(cache_option) != len(set(cache_option)): for idx in xrange(1, len(cache_option) + 1): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 8e26688..ebfab5a 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -19,9 +19,9 @@ from time import time from copy import copy from logging import getLogger import weakref -from tiramisu.error import (RequirementError, PropertiesOptionError, - ConstError, ConfigError) -from tiramisu.i18n import _ +from .error import (RequirementError, PropertiesOptionError, + ConstError, ConfigError) +from .i18n import _ "Default encoding for display a Config if raise UnicodeEncodeError" @@ -367,7 +367,7 @@ class Settings(object): def _getproperties(self, opt=None, path=None, setting_properties=undefined, read_write=True, - apply_requires=True): + apply_requires=True, index=None): """ """ if opt is None: @@ -384,17 +384,17 @@ class Settings(object): ntime = int(time()) else: ntime = None - if 'cache' in setting_properties and self._p_.hascache(path): - is_cached, props = self._p_.getcache(path, ntime) + if 'cache' in setting_properties and self._p_.hascache(path, index): + is_cached, props = self._p_.getcache(path, ntime, index) if not is_cached: props = self._p_.getproperties(path, opt.impl_getproperties()) if apply_requires: props = copy(props) - props |= self.apply_requires(opt, path, setting_properties) + props |= self.apply_requires(opt, path, setting_properties, index) if 'cache' in setting_properties: if 'expire' in setting_properties: ntime = ntime + expires_time - self._p_.setcache(path, props, ntime) + self._p_.setcache(path, props, ntime, index) if read_write: props = copy(props) return props @@ -458,7 +458,8 @@ class Settings(object): properties = copy(self_properties) else: properties = self._getproperties(opt_or_descr, path, - setting_properties=setting_properties) + setting_properties=setting_properties, + index=index) # remove opt permissive # permissive affect option's permission with or without permissive # global property @@ -571,7 +572,7 @@ class Settings(object): else: self._p_.reset_all_cache() - def apply_requires(self, opt, path, setting_properties): + def apply_requires(self, opt, path, setting_properties, index): """carries out the jit (just in time) requirements between options a requirement is a tuple of this form that comes from the option's @@ -633,7 +634,8 @@ class Settings(object): "'{1}'").format(path, reqpath)) try: value = context.getattr(reqpath, force_permissive=True, - _setting_properties=setting_properties) + _setting_properties=setting_properties, + index=index) except PropertiesOptionError as err: if not transitive: continue diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index cd63676..8cabd6d 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -29,8 +29,11 @@ configurator ``set_storage()``. from time import time from random import randint import os -from tiramisu.error import ConfigError -from tiramisu.i18n import _ +from ..error import ConfigError +from ..i18n import _ + + +MODULE_PATH = os.path.split(os.path.split(os.path.split(__file__)[0])[0])[1] class StorageType(object): @@ -53,7 +56,7 @@ class StorageType(object): if self.storage_type is None: self.storage_type = self.default_storage if self.mod is None: - modulepath = 'tiramisu.storage.{0}'.format(self.storage_type) + modulepath = '{0}.storage.{1}'.format(MODULE_PATH, self.storage_type) try: mod = __import__(modulepath) except ImportError: diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index f7d22b1..4863119 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -17,9 +17,9 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ -from tiramisu.i18n import _ -from tiramisu.setting import undefined -from tiramisu.error import ConfigError +from ...i18n import _ +from ...setting import undefined +from ...error import ConfigError #____________________________________________________________ diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 252d29e..e9c257b 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from tiramisu.i18n import _ -from tiramisu.error import ConfigError +from ...i18n import _ +from ...error import ConfigError from ..util import SerializeObject diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index 7b663fd..6b50054 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -16,7 +16,7 @@ # along with this program. If not, see . # ____________________________________________________________ from ..util import Cache -from tiramisu.setting import undefined +from ...setting import undefined class Values(Cache): diff --git a/tiramisu/storage/util.py b/tiramisu/storage/util.py index 170232f..b118913 100644 --- a/tiramisu/storage/util.py +++ b/tiramisu/storage/util.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ -from tiramisu.setting import owners +from ..setting import owners class SerializeObject(object): @@ -97,27 +97,30 @@ class Cache(object): value = tuple(_value) setattr(self, key, value) - def setcache(self, path, val, time): - self._cache[path] = (val, time) + def setcache(self, path, val, time, index): + self._cache.setdefault(path, {})[index] = (val, time) - def getcache(self, path, exp): - value, created = self._cache[path] + def getcache(self, path, exp, index): + value, created = self._cache[path][index] if created is None or exp <= created: return True, value return False, None - def hascache(self, path): + def hascache(self, path, index): """ path is in the cache :param path: the path's option """ - return path in self._cache + return path in self._cache and index in self._cache[path] def reset_expired_cache(self, exp): - for key in tuple(self._cache.keys()): - val, created = self._cache[key] - if created is not None and exp > created: - del(self._cache[key]) + for key in self._cache.keys(): + for index in self._cache[key].keys(): + val, created = self._cache[key][index] + if created is not None and exp > created: + del(self._cache[key][index]) + if self._cache[key] == {}: + del(self._cache[key]) def reset_all_cache(self): "empty the cache" @@ -125,6 +128,6 @@ class Cache(object): def get_cached(self, context): """return all values in a dictionary - example: {'path1': ('value1', 'time1'), 'path2': ('value2', 'time2')} + example: {'path1': {'index1': ('value1', 'time1')}, 'path2': {'index2': ('value2', 'time2', )}} """ return self._cache diff --git a/tiramisu/value.py b/tiramisu/value.py index 2e0deb3..23d3fd4 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -18,11 +18,11 @@ from time import time import sys import weakref -from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError -from tiramisu.setting import owners, expires_time, undefined -from tiramisu.autolib import carry_out_calculation -from tiramisu.i18n import _ -from tiramisu.option import SymLinkOption, DynSymLinkOption, Option +from .error import ConfigError, SlaveError, PropertiesOptionError +from .setting import owners, expires_time, undefined +from .autolib import carry_out_calculation +from .i18n import _ +from .option import SymLinkOption, DynSymLinkOption, Option class Values(object): @@ -65,7 +65,7 @@ class Values(object): callback=callback, callback_params=callback_params, index=index) - if isinstance(value, list) and index is not undefined: + if isinstance(value, list) and index is not None: #if return a list and index is set, return value only if #it's a submulti without submulti_index and without list of list if opt.impl_is_submulti() and submulti_index is undefined and \ @@ -78,9 +78,9 @@ class Values(object): if meta is not None: try: value = meta.cfgimpl_get_values( - )._get_cached_item(opt, path) + )._get_cached_value(opt, path, index=index, from_masterslave=True) if isinstance(value, Multi): - if index is not undefined: + if index is not None: value = value[index] else: value = list(value) @@ -89,7 +89,7 @@ class Values(object): pass # now try to get default value value = opt.impl_getdefault() - if opt.impl_is_multi() and index is not undefined: + if opt.impl_is_multi() and index is not None: if value == []: value = opt.impl_getdefault_multi() else: @@ -115,7 +115,7 @@ class Values(object): return self._p_.getvalue(path, index) else: value = self._p_.getvalue(path) - if index is not undefined: + if index is not None: try: return value[index] except IndexError: @@ -204,13 +204,15 @@ class Values(object): def getitem(self, opt, validate=True, force_permissive=False): """ """ - return self._get_cached_item(opt, validate=validate, - force_permissive=force_permissive) + return self._get_cached_value(opt, validate=validate, + force_permissive=force_permissive) - def _get_cached_item(self, opt, path=None, validate=True, - force_permissive=False, force_properties=None, - validate_properties=True, - setting_properties=undefined, self_properties=undefined): + def _get_cached_value(self, opt, path=None, validate=True, + force_permissive=False, force_properties=None, + validate_properties=True, + setting_properties=undefined, self_properties=undefined, + index=None, from_masterslave=False, with_meta=True, + masterlen=undefined): untrusted_cached_properties = force_properties is None context = self._getcontext() if path is None: @@ -221,13 +223,14 @@ class Values(object): )._getproperties(read_write=False) if self_properties is undefined: self_properties = context.cfgimpl_get_settings()._getproperties( - opt, path, read_write=False, setting_properties=setting_properties) - if 'cache' in setting_properties and self._p_.hascache(path): + opt, path, read_write=False, + setting_properties=setting_properties, index=index) + if 'cache' in setting_properties and self._p_.hascache(path, index): if 'expire' in setting_properties: ntime = int(time()) - is_cached, value = self._p_.getcache(path, ntime) + is_cached, value = self._p_.getcache(path, ntime, index) if is_cached: - if opt.impl_is_multi() and not isinstance(value, Multi): + if opt.impl_is_multi() and not isinstance(value, Multi) and index is None: value = Multi(value, self.context, opt, path) if not untrusted_cached_properties: # revalidate properties (because not default properties) @@ -236,42 +239,41 @@ class Values(object): force_permissive=force_permissive, force_properties=force_properties, setting_properties=setting_properties, - self_properties=self_properties) + self_properties=self_properties, + index=index) return value - val = self._getitem(opt, path, validate, force_permissive, - force_properties, validate_properties, - setting_properties, self_properties=self_properties) - if 'cache' in setting_properties and validate and validate_properties \ + if not from_masterslave and opt.impl_is_master_slaves(): + val = opt.impl_get_master_slaves().getitem(self, opt, path, + validate, + force_permissive, + force_properties, + validate_properties, + setting_properties=setting_properties, + index=index, + self_properties=self_properties) + else: + val = self._get_validated_value(opt, path, validate, + force_permissive, + force_properties, + validate_properties, + setting_properties=setting_properties, + self_properties=self_properties, + with_meta=with_meta, + masterlen=masterlen, + index=index) + # cache doesn't work with SubMulti yet + if not isinstance(val, SubMulti) and 'cache' in setting_properties and validate and validate_properties \ and force_permissive is False and force_properties is None: if 'expire' in setting_properties: if ntime is None: ntime = int(time()) ntime = ntime + expires_time - self._p_.setcache(path, val, ntime) + self._p_.setcache(path, val, ntime, index) return val - def _getitem(self, opt, path, validate, force_permissive, force_properties, - validate_properties, setting_properties=undefined, - self_properties=undefined): - if opt.impl_is_master_slaves(): - return opt.impl_get_master_slaves().getitem(self, opt, path, - validate, - force_permissive, - force_properties, - validate_properties, - setting_properties=setting_properties, - self_properties=self_properties) - else: - return self._get_validated_value(opt, path, validate, - force_permissive, - force_properties, - validate_properties, - setting_properties=setting_properties, - self_properties=self_properties) - def _get_validated_value(self, opt, path, validate, force_permissive, force_properties, validate_properties, - index=undefined, submulti_index=undefined, + index=None, submulti_index=undefined, with_meta=True, setting_properties=undefined, self_properties=undefined, masterlen=undefined): """same has getitem but don't touch the cache @@ -282,7 +284,7 @@ class Values(object): if setting_properties is undefined: setting_properties = setting._getproperties(read_write=False) if self_properties is undefined: - self_properties = setting._getproperties(opt, path, read_write=False) + self_properties = setting._getproperties(opt, path, read_write=False, index=index) is_default = self._is_default_owner(opt, path, validate_properties=False, validate_meta=False, @@ -458,8 +460,8 @@ class Values(object): if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties: return owners.default if validate_properties: - self._getitem(opt, path, True, force_permissive, None, True, - self_properties=self_properties) + self._get_cached_value(opt, path, True, force_permissive, None, True, + self_properties=self_properties) owner = self._p_.getowner(path, owners.default, only_default=only_default, index=index) if validate_meta is undefined: if opt.impl_is_master_slaves('slave'): @@ -601,12 +603,12 @@ class Values(object): setting_properties=setting_properties) if 'mandatory' in self_properties: try: - self._get_cached_item(true_opt, path=true_path, - force_properties=frozenset(('mandatory',)), - force_permissive=force_permissive, - setting_properties=setting_properties, - self_properties=self_properties, - validate=validate) + self._get_cached_value(true_opt, path=true_path, + force_properties=frozenset(('mandatory',)), + force_permissive=force_permissive, + setting_properties=setting_properties, + self_properties=self_properties, + validate=validate) except PropertiesOptionError as err: if err.proptype == ['mandatory']: yield path @@ -710,7 +712,7 @@ class Multi(list): setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties and validate: fake_context = context._gen_fake_values() - fake_multi = fake_context.cfgimpl_get_values()._get_cached_item( + fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) fake_multi._setitem(index, value, validate=False) self._validate(value, fake_context, index, True) @@ -721,8 +723,11 @@ class Multi(list): #def __repr__(self, *args, **kwargs): # return super(Multi, self).__repr__(*args, **kwargs) - #def __getitem__(self, y): - # return super(Multi, self).__getitem__(y) + def __getitem__(self, index): + value = super(Multi, self).__getitem__(index) + if isinstance(value, PropertiesOptionError): + raise value + return value def _get_validated_value(self, index): values = self._getcontext().cfgimpl_get_values() @@ -740,15 +745,16 @@ class Multi(list): index = self.__len__() if value is undefined: value = self._get_validated_value(index) - context = self._getcontext() - setting = context.cfgimpl_get_settings() - setting_properties = setting._getproperties(read_write=False) - if 'validator' in setting_properties and validate and value not in [None, undefined]: - fake_context = context._gen_fake_values() - fake_multi = fake_context.cfgimpl_get_values()._get_cached_item( - self.opt, path=self.path, validate=False) - fake_multi.append(value, validate=False, force=True) - self._validate(value, fake_context, index, True) + if validate and value not in [None, undefined]: + context = self._getcontext() + setting = context.cfgimpl_get_settings() + setting_properties = setting._getproperties(read_write=False) + if 'validator' in setting_properties: + fake_context = context._gen_fake_values() + fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( + self.opt, path=self.path, validate=False) + fake_multi.append(value, validate=False, force=True) + self._validate(value, fake_context, index, True) if not '_index' in self.__slots__ and self.opt.impl_is_submulti(): if not isinstance(value, SubMulti): value = SubMulti(value, self.context, self.opt, self.path, index) @@ -757,6 +763,9 @@ class Multi(list): if setitem: self._store(force=force) + def append_properties_error(self, err): + super(Multi, self).append(err) + def sort(self, cmp=None, key=None, reverse=False): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot sort multi option {0} if master or slave" @@ -786,7 +795,7 @@ class Multi(list): setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties and validate and value is not None: fake_context = context._gen_fake_values() - fake_multi = fake_context.cfgimpl_get_values()._get_cached_item( + fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) fake_multi.insert(index, value, validate=False) self._validate(value, fake_context, index, True) @@ -806,7 +815,7 @@ class Multi(list): setting_properties = setting._getproperties(read_write=False) if 'validator' in setting_properties and validate: fake_context = context._gen_fake_values() - fake_multi = fake_context.cfgimpl_get_values()._get_cached_item( + fake_multi = fake_context.cfgimpl_get_values()._get_cached_value( self.opt, path=self.path, validate=False) fake_multi.extend(iterable, validate=False) self._validate(iterable, fake_context, index)