diff --git a/ChangeLog b/ChangeLog index e5980ff..68f2dbc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Sun Jul 26 19:09:29 2015 +0200 Emmanuel Garette + * add global 'empty' property, this property raise mandatory + PropertiesOptionError if multi or master have empty value + Fri Jul 24 18:03:59 2015 +0200 Emmanuel Garette * add duplicate option to Config, to generate new Config with same value, properties, Option. Option are not duplication. diff --git a/test/test_mandatory.py b/test/test_mandatory.py index 69ea2ad..2c13c1e 100644 --- a/test/test_mandatory.py +++ b/test/test_mandatory.py @@ -1,12 +1,13 @@ +# coding: utf-8 from autopath import do_autopath do_autopath() - from time import sleep -#from py.test import raises +from py.test import raises from tiramisu.config import Config from tiramisu.option import StrOption, UnicodeOption, OptionDescription from tiramisu.error import PropertiesOptionError +from tiramisu.setting import groups def make_description(): @@ -316,3 +317,87 @@ def test_mandatory_warnings_frozen(): config.read_only() assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3'] sleep(.1) + + +def test_mandatory_master(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True, + properties=('mandatory', )) + 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) + o = OptionDescription('o', '', [interface1]) + config = Config(o) + config.read_only() + raises(PropertiesOptionError, 'config.ip_admin_eth0.ip_admin_eth0') + raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0') + + +def test_mandatory_master_empty(): + 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) + o = OptionDescription('o', '', [interface1]) + config = Config(o) + config.read_write() + assert config.ip_admin_eth0.ip_admin_eth0 == [] + assert config.ip_admin_eth0.netmask_admin_eth0 == [] + # + config.ip_admin_eth0.ip_admin_eth0.append() + assert config.ip_admin_eth0.ip_admin_eth0 == [None] + assert config.ip_admin_eth0.netmask_admin_eth0 == [None] + config.read_only() + raises(PropertiesOptionError, "config.ip_admin_eth0.ip_admin_eth0") + raises(PropertiesOptionError, "config.ip_admin_eth0.netmask_admin_eth0") + config.read_write() + del(config.ip_admin_eth0.ip_admin_eth0) + del(config.ip_admin_eth0.netmask_admin_eth0) + assert config.ip_admin_eth0.ip_admin_eth0 == [] + assert config.ip_admin_eth0.netmask_admin_eth0 == [] + # + config.ip_admin_eth0.ip_admin_eth0.append('') + assert config.ip_admin_eth0.ip_admin_eth0 == [''] + assert config.ip_admin_eth0.netmask_admin_eth0 == [None] + config.read_only() + raises(PropertiesOptionError, "config.ip_admin_eth0.ip_admin_eth0") + raises(PropertiesOptionError, "config.ip_admin_eth0.netmask_admin_eth0") + config.read_write() + # + config.read_write() + config.ip_admin_eth0.ip_admin_eth0 = ['ip'] + config.read_only() + assert config.ip_admin_eth0.ip_admin_eth0 == ['ip'] + assert config.ip_admin_eth0.netmask_admin_eth0 == [None] + + +def test_mandatory_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, properties=('mandatory', )) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + o = OptionDescription('o', '', [interface1]) + config = Config(o) + config.read_only() + assert config.ip_admin_eth0.ip_admin_eth0 == [] + assert config.ip_admin_eth0.netmask_admin_eth0 == [] + # + config.read_write() + config.ip_admin_eth0.ip_admin_eth0.append('ip') + config.read_only() + assert config.ip_admin_eth0.ip_admin_eth0 == ['ip'] + raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0') + # + config.read_write() + config.ip_admin_eth0.netmask_admin_eth0 = [''] + config.read_only() + assert config.ip_admin_eth0.ip_admin_eth0 == ['ip'] + raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0') + # + config.read_write() + config.ip_admin_eth0.netmask_admin_eth0 = ['ip'] + config.read_only() + assert config.ip_admin_eth0.ip_admin_eth0 == ['ip'] + assert config.ip_admin_eth0.netmask_admin_eth0 == ['ip'] diff --git a/test/test_multi.py b/test/test_multi.py index 6d06011..29c9ef1 100644 --- a/test/test_multi.py +++ b/test/test_multi.py @@ -3,9 +3,9 @@ from autopath import do_autopath do_autopath() from tiramisu.value import Multi -from tiramisu.option import IntOption, OptionDescription +from tiramisu.option import IntOption, StrOption, OptionDescription from tiramisu.config import Config -from tiramisu.error import ConfigError +from tiramisu.error import ConfigError, PropertiesOptionError import weakref from py.test import raises @@ -21,3 +21,21 @@ def test_multi(): assert c is multi._getcontext() del(c) raises(ConfigError, "multi._getcontext()") + + +def test_multi_none(): + s = StrOption('str', '', multi=True) + o = OptionDescription('od', '', [s]) + c = Config(o) + c.read_only() + assert c.str == [] + c.read_write() + c.str.append(None) + assert c.str == [None] + c.read_only() + raises(PropertiesOptionError, "c.str") + c.read_write() + c.str = [''] + assert c.str == [''] + c.read_only() + raises(PropertiesOptionError, "c.str") diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 88c24f9..6f0564e 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -99,7 +99,7 @@ class Base(StorageBase): def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, - properties=None, warnings_only=False, extra=None, allow_empty_list=False): + properties=None, warnings_only=False, extra=None, allow_empty_list=undefined): if not valid_name(name): # pragma: optional cover raise ValueError(_("invalid name: {0} for option").format(name)) if requires is not None: @@ -900,7 +900,7 @@ class SymLinkOption(OnlyOption): 'for symlink {0}').format(name)) super(Base, self).__init__(name, undefined, undefined, undefined, undefined, undefined, undefined, undefined, - False, opt) + undefined, opt) self.commit() def __getattr__(self, name, context=undefined): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index ba63853..f371e5b 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -75,6 +75,9 @@ everything_frozen whole option in config are frozen (even if option have not frozen property) +empty + raise mandatory PropertiesOptionError if multi or master have empty value + validator launch validator set by user in option (this property has no effect for internal validator) @@ -97,10 +100,10 @@ read_write you can set all variables not frozen """ ro_append = set(['frozen', 'disabled', 'validator', 'everything_frozen', - 'mandatory']) + 'mandatory', 'empty']) ro_remove = set(['permissive', 'hidden']) rw_append = set(['frozen', 'disabled', 'validator', 'hidden']) -rw_remove = set(['permissive', 'everything_frozen', 'mandatory']) +rw_remove = set(['permissive', 'everything_frozen', 'mandatory', 'empty']) forbidden_set_properties = set(['force_store_value']) @@ -468,8 +471,13 @@ class Settings(object): else: if 'mandatory' in properties and \ not self._getcontext().cfgimpl_get_values()._isempty( - opt_or_descr, value, opt_or_descr.impl_allow_empty_list()): + opt_or_descr, value): properties.remove('mandatory') + elif not is_write and 'empty' in forced_properties and \ + not opt_or_descr.impl_is_master_slaves('slave') and \ + self._getcontext().cfgimpl_get_values()._isempty( + opt_or_descr, value, force_allow_empty_list=True): + properties.add('mandatory') if is_write and 'everything_frozen' in forced_properties: properties.add('frozen') elif 'frozen' in properties and not is_write: diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index 219e606..bab035f 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -79,7 +79,7 @@ class StorageBase(object): self._properties = properties if opt is not undefined: self._opt = opt - if allow_empty_list is not False: + if allow_empty_list is not undefined: self._allow_empty_list = allow_empty_list def _set_default_values(self, default, default_multi): @@ -302,8 +302,7 @@ class StorageBase(object): try: return self._allow_empty_list except AttributeError: - return False - + return undefined def _get_extra(self, key): extra = self._extra diff --git a/tiramisu/value.py b/tiramisu/value.py index 2ff1fe5..c5f45e5 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -175,18 +175,27 @@ class Values(object): if hasvalue: self._p_.resetvalue(path) - def _isempty(self, opt, value, allow_empty_list): + def _isempty(self, opt, value, force_allow_empty_list=False): "convenience method to know if an option is empty" - empty = opt._empty - if value is not undefined: - empty_not_multi = not opt.impl_is_multi() and (value is None or - value == empty) - empty_multi = opt.impl_is_multi() and ((not allow_empty_list and value == []) or - None in value or - empty in value) + if value is undefined: + return False else: - empty_multi = empty_not_multi = False - return empty_not_multi or empty_multi + empty = opt._empty + if opt.impl_is_multi(): + if force_allow_empty_list: + allow_empty_list = True + else: + allow_empty_list = opt.impl_allow_empty_list() + if allow_empty_list is undefined: + if opt.impl_is_master_slaves('slave'): + allow_empty_list = True + else: + allow_empty_list = False + isempty = (not allow_empty_list and value == []) or \ + None in value or empty in value + else: + isempty = value is None or value == empty + return isempty def __getitem__(self, opt): "enables us to use the pythonic dictionary-like access to values"