diff --git a/ChangeLog b/ChangeLog index f52827a..9f17778 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Mon Apr 20 14:44:15 2015 +0200 Emmanuel Garette + * if option is multi, the properties disallow [None] for a multi but + [] too, with allow_empty_list to True, [None] is not allowed, but you + can have empty list (so []) + + Sun Apr 19 09:14:21 2015 +0200 Emmanuel Garette * valid Option is an unicode or a string if needed * difference between option/optiondescription in PropertiesOptionError diff --git a/test/test_mandatory.py b/test/test_mandatory.py index ae73418..374e2aa 100644 --- a/test/test_mandatory.py +++ b/test/test_mandatory.py @@ -16,7 +16,9 @@ def make_description(): properties=('mandatory', )) stroption3 = StrOption('str3', 'Test string option', multi=True, properties=('mandatory', )) - descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3]) + stroption4 = StrOption('str4', 'Test string option', multi=True, + properties=('mandatory', ), allow_empty_list=True) + descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3, stroption4]) return descr @@ -123,6 +125,17 @@ def test_mandatory_multi_none(): def test_mandatory_multi_empty(): descr = make_description() config = Config(descr) + config.str3 = [] + assert config.getowner(config.unwrap_from_path('str3')) == 'user' + config.read_only() + prop = [] + try: + config.str3 + except PropertiesOptionError as err: + prop = err.proptype + assert 'mandatory' in prop + # + config.read_write() config.str3 = [''] assert config.getowner(config.unwrap_from_path('str3')) == 'user' config.read_only() @@ -132,6 +145,7 @@ def test_mandatory_multi_empty(): except PropertiesOptionError as err: prop = err.proptype assert 'mandatory' in prop + # config.read_write() config.str3 = ['yes', ''] assert config.getowner(config.unwrap_from_path('str3')) == 'user' @@ -144,6 +158,38 @@ def test_mandatory_multi_empty(): assert 'mandatory' in prop +def test_mandatory_multi_empty_allow_empty_list(): + descr = make_description() + config = Config(descr) + config.str4 = [] + assert config.getowner(config.unwrap_from_path('str4')) == 'user' + config.read_only() + prop = [] + config.str4 + # + config.read_write() + config.str4 = [''] + assert config.getowner(config.unwrap_from_path('str4')) == 'user' + config.read_only() + prop = [] + try: + config.str4 + except PropertiesOptionError as err: + prop = err.proptype + assert 'mandatory' in prop + # + config.read_write() + config.str4 = ['yes', ''] + assert config.getowner(config.unwrap_from_path('str4')) == 'user' + config.read_only() + prop = [] + try: + config.str4 + except PropertiesOptionError as err: + prop = err.proptype + assert 'mandatory' in prop + + def test_mandatory_multi_append(): descr = make_description() config = Config(descr) diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index ef57b71..5cece57 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): + properties=None, warnings_only=False, extra=None, allow_empty_list=False): if not valid_name(name): # pragma: optional cover raise ValueError(_("invalid name: {0} for option").format(name)) if requires is not None: @@ -134,7 +134,7 @@ class Base(StorageBase): 'requirement {0}'.format( list(set_forbidden_properties))) StorageBase.__init__(self, name, _multi, warnings_only, doc, extra, - calc_properties, requires, properties) + calc_properties, requires, properties, allow_empty_list) if multi is not False and default is None: default = [] self.impl_validate(default) @@ -905,7 +905,7 @@ class SymLinkOption(OnlyOption): 'for symlink {0}').format(name)) super(Base, self).__init__(name, undefined, undefined, undefined, undefined, undefined, undefined, undefined, - opt) + False, opt) self.commit() def __getattr__(self, name, context=undefined): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index dfe48a4..c3dd947 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -467,7 +467,7 @@ class Settings(object): else: if 'mandatory' in properties and \ not self._getcontext().cfgimpl_get_values()._isempty( - opt_or_descr, value): + opt_or_descr, value, opt_or_descr.impl_allow_empty_list()): properties.remove('mandatory') if is_write and 'everything_frozen' in self_properties: properties.add('frozen') diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index f6b19b3..219e606 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -32,7 +32,8 @@ class StorageBase(object): '_multi', '_extra', '_warnings_only', - #valeur + '_allow_empty_list', + #value '_default', '_default_multi', #calcul @@ -41,10 +42,7 @@ class StorageBase(object): '_properties', '_calc_properties', '_val_call', - #'_validator', - #'_validator_params', - #'_callback', - #'_callback_params', + # '_consistencies', '_master_slaves', '_choice_values', @@ -62,7 +60,7 @@ class StorageBase(object): ) def __init__(self, name, multi, warnings_only, doc, extra, calc_properties, - requires, properties, opt=undefined): + requires, properties, allow_empty_list, opt=undefined): self._name = name if doc is not undefined: self._informations = {'doc': doc} @@ -81,6 +79,8 @@ class StorageBase(object): self._properties = properties if opt is not undefined: self._opt = opt + if allow_empty_list is not False: + self._allow_empty_list = allow_empty_list def _set_default_values(self, default, default_multi): if ((self.impl_is_multi() and default != tuple()) or @@ -298,6 +298,13 @@ class StorageBase(object): _multi = 1 return _multi == 2 + def impl_allow_empty_list(self): + try: + return self._allow_empty_list + except AttributeError: + return False + + def _get_extra(self, key): extra = self._extra if isinstance(extra, tuple): @@ -308,7 +315,7 @@ class StorageBase(object): def _is_warnings_only(self): try: return self._warnings_only - except: + except AttributeError: return False def impl_getdefault(self): diff --git a/tiramisu/value.py b/tiramisu/value.py index 6c14315..2305d6f 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -175,13 +175,13 @@ class Values(object): if hasvalue: self._p_.resetvalue(path) - def _isempty(self, opt, value): + def _isempty(self, opt, value, allow_empty_list): "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 (value == [] or + empty_multi = opt.impl_is_multi() and ((not allow_empty_list and value == []) or None in value or empty in value) else: