add unique parameter to option

This commit is contained in:
Emmanuel Garette 2016-11-19 19:16:31 +01:00
parent fc36f674eb
commit 42d830687d
7 changed files with 116 additions and 33 deletions

View File

@ -1,5 +1,7 @@
Wed Nov 16 22:30:12 2016 +0200 Emmanuel Garette <egarette@cadoles.com>
* consistency "not_equal" works now with multi
* consistency "not_equal" works now with multi and submulti
* a multi or submulti could be "unique" (same value one time)
* consistency "not_equal" means "unique" too
Wed Oct 12 21:55:53 2016 +0200 Emmanuel Garette <egarette@cadoles.com>
* consistency is now check "not_equal" if one option has

View File

@ -24,6 +24,25 @@ def test_multi():
raises(ConfigError, "multi._getcontext()")
def test_multi_unique():
i = IntOption('int', '', multi=True, unique=True)
o = OptionDescription('od', '', [i])
c = Config(o)
assert c.int == []
c.int = [0]
assert c.int == [0]
raises(ValueError, "c.int = [0, 0]")
raises(ValueError, "c.int = [1, 0, 2, 3, 4, 5, 6, 0, 7]")
raises(ValueError, "c.int.append(0)")
raises(ValueError, "c.int.extend([1, 2, 1, 3])")
raises(ValueError, "c.int.extend([1, 2, 0, 3])")
c.int.extend([4, 5, 6])
def test_non_multi_unique():
raises(ValueError, "IntOption('int', '', unique=True)")
def test_multi_none():
s = StrOption('str', '', multi=True)
o = OptionDescription('od', '', [s])

View File

@ -4,7 +4,7 @@ do_autopath()
from tiramisu.setting import groups, owners
from tiramisu.config import Config
from tiramisu.option import StrOption, OptionDescription, submulti
from tiramisu.option import StrOption, IntOption, OptionDescription, submulti
from tiramisu.value import SubMulti, Multi
from tiramisu.error import SlaveError
@ -663,3 +663,19 @@ def test_callback_submulti():
assert cfg.getowner(multi2) == owners.default
assert cfg.multi == [['val']]
assert cfg.multi2 == [['val']]
def test_submulti_unique():
i = IntOption('int', '', multi=submulti, unique=True)
o = OptionDescription('od', '', [i])
c = Config(o)
assert c.int == []
c.int = [[0]]
assert c.int == [[0]]
raises(ValueError, "c.int = [[0, 0]]")
c.int = [[0], [0]]
raises(ValueError, "c.int[0] = [1, 0, 2, 3, 4, 5, 6, 0, 7]")
raises(ValueError, "c.int[0].append(0)")
raises(ValueError, "c.int[0].extend([1, 2, 1, 3])")
raises(ValueError, "c.int[0].extend([1, 2, 0, 3])")
c.int[0].extend([4, 5, 6])

View File

@ -102,7 +102,7 @@ class Base(StorageBase):
__slots__ = tuple()
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
requires=None, multi=False, unique=undefined, callback=None,
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False, extra=None,
allow_empty_list=undefined, session=None):
@ -122,6 +122,10 @@ class Base(StorageBase):
_multi = submulti
else:
raise ValueError(_('invalid multi value'))
if unique != undefined and not isinstance(unique, bool):
raise ValueError(_('unique must be a boolean'))
if not is_multi and unique == True:
raise ValueError(_('unique must be set only with multi value'))
if requires is not None:
calc_properties, requires = validate_requires_arg(is_multi,
requires, name)
@ -149,7 +153,7 @@ class Base(StorageBase):
session = self.getsession()
StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
calc_properties, requires, properties,
allow_empty_list, session=session)
allow_empty_list, unique, session=session)
if multi is not False and default is None:
default = []
err = self.impl_validate(default, is_multi=is_multi)
@ -175,7 +179,7 @@ class Base(StorageBase):
" yet for option {0}").format(
self.impl_getname()))
if not _init and self.impl_get_callback()[0] is not None:
raise ConfigError(_("a callback is already set for option {0}, "
raise ConfigError(_("a callback is already set for {0}, "
"cannot set another one's").format(self.impl_getname()))
self._validate_callback(callback, callback_params)
if callback is not None:
@ -415,9 +419,8 @@ class Option(OnlyOption):
all_cons_opts = []
val_consistencies = True
for opt in opts:
is_multi = opt.impl_is_multi() and not opt.impl_is_master_slaves()
if not is_multi and ((isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
option == opt):
if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
option == opt:
# option is current option
# we have already value, so use it
all_cons_vals.append(value)
@ -425,6 +428,7 @@ class Option(OnlyOption):
else:
#if context, calculate value, otherwise get default value
path = None
is_multi = opt.impl_is_multi() and not opt.impl_is_master_slaves()
if context is not undefined:
if isinstance(opt, DynSymLinkOption):
path = opt.impl_getpath(context)
@ -470,7 +474,7 @@ class Option(OnlyOption):
def impl_validate(self, value, context=undefined, validate=True,
force_index=None, force_submulti_index=None,
current_opt=undefined, is_multi=None,
display_warnings=True):
display_warnings=True, multi=None):
"""
:param value: the option's value
:param context: Config's context
@ -489,6 +493,13 @@ class Option(OnlyOption):
if current_opt is undefined:
current_opt = self
def _is_not_unique(value):
if self.impl_is_unique() and len(set(value)) != len(value):
for idx, val in enumerate(value):
if val in value[idx+1:]:
return ValueError(_('invalid value "{}", this value is already in "{}"').format(
val, self.impl_get_display_name()))
def calculation_validator(val):
validator, validator_params = self.impl_get_validator()
if validator is not None:
@ -589,43 +600,60 @@ class Option(OnlyOption):
if is_multi is None:
is_multi = self.impl_is_multi()
if not is_multi:
return do_validation(value, None, None)
elif force_index is not None:
if self.impl_is_submulti() and force_submulti_index is None:
err = _is_not_unique(value)
if err:
return err
if not isinstance(value, list): # pragma: optional cover
raise ValueError(_("invalid value {0} for option {1} which"
" must be a list").format(
value, self.impl_getname()))
return ValueError(_('invalid value "{0}" for "{1}" which'
' must be a list').format(
value, self.impl_get_display_name()))
for idx, val in enumerate(value):
if isinstance(val, list):
return ValueError(_('invalid value "{}" for "{}" '
'which must not be a list'.format(val,
self.impl_get_display_name())))
err = do_validation(val, force_index, idx)
if err:
return err
else:
if self.impl_is_unique() and value in multi:
return ValueError(_('invalid value "{}", this value is already'
' in "{}"').format(value,
self.impl_get_display_name()))
return do_validation(value, force_index, force_submulti_index)
elif not isinstance(value, list): # pragma: optional cover
return ValueError(_("invalid value {0} for option {1} which "
"must be a list").format(value,
return ValueError(_('invalid value "{0}" for "{1}" which '
'must be a list').format(value,
self.impl_getname()))
elif self.impl_is_submulti() and force_submulti_index is None:
for idx, val in enumerate(value):
err = _is_not_unique(val)
if err:
return err
if not isinstance(val, list): # pragma: optional cover
return ValueError(_("invalid value {0} for option {1} "
"which must be a list of list"
"").format(value,
return ValueError(_('invalid value "{0}" for "{1}" '
'which must be a list of list'
'').format(val,
self.impl_getname()))
for slave_idx, slave_val in enumerate(val):
err = do_validation(slave_val, idx, slave_idx)
if err:
return err
else:
err = _is_not_unique(value)
if err:
return err
for idx, val in enumerate(value):
err = do_validation(val, idx, force_submulti_index)
if err:
return err
else:
return self._valid_consistency(current_opt, None, context,
None, None)
return self._valid_consistency(current_opt, None, context,
None, None)
def impl_is_dynsymlinkoption(self):
return False
@ -717,6 +745,10 @@ class Option(OnlyOption):
if err:
self._del_consistency()
raise err
if func in allowed_const_list:
for opt in all_cons_opts:
if getattr(opt, '_unique', undefined) == undefined:
opt._unique = True
#consistency could generate warnings or errors
self._set_has_dependency()
@ -948,7 +980,7 @@ class SymLinkOption(OnlyOption):
session = self.getsession()
super(Base, self).__init__(name, undefined, undefined, undefined,
undefined, undefined, undefined, undefined,
undefined, opt, session=session)
undefined, undefined, opt=opt, session=session)
opt._set_has_dependency()
self.commit(session)
@ -1030,13 +1062,14 @@ class DynSymLinkOption(object):
def impl_validate(self, value, context=undefined, validate=True,
force_index=None, force_submulti_index=None, is_multi=None,
display_warnings=True):
display_warnings=True, multi=None):
return self._impl_getopt().impl_validate(value, context, validate,
force_index,
force_submulti_index,
current_opt=self,
is_multi=is_multi,
display_warnings=display_warnings)
display_warnings=display_warnings,
multi=multi)
def impl_is_dynsymlinkoption(self):
return True

View File

@ -310,7 +310,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
if isinstance(values, Exception):
raise values
if len(values) > len(set(values)):
raise ConfigError(_('DynOptionDescription callback return not uniq value'))
raise ConfigError(_('DynOptionDescription callback return not unique value'))
for val in values:
if not isinstance(val, str) or re.match(name_regexp, val) is None:
raise ValueError(_("invalid suffix: {0} for option").format(val))

View File

@ -34,10 +34,12 @@ if sys.version_info[0] >= 3: # pragma: optional cover
class StorageBase(object):
__slots__ = ('_name',
'_informations',
'_multi',
'_extra',
'_warnings_only',
'_allow_empty_list',
#multi
'_multi',
'_unique',
#value
'_default',
'_default_multi',
@ -66,7 +68,7 @@ class StorageBase(object):
)
def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
requires, properties, allow_empty_list, opt=undefined,
requires, properties, allow_empty_list, unique, opt=undefined,
session=None):
_setattr = object.__setattr__
_setattr(self, '_name', name)
@ -89,6 +91,8 @@ class StorageBase(object):
_setattr(self, '_opt', opt)
if allow_empty_list is not undefined:
_setattr(self, '_allow_empty_list', allow_empty_list)
if unique is not undefined:
setattr(self, '_unique', unique)
def _set_default_values(self, default, default_multi, is_multi):
_setattr = object.__setattr__
@ -343,6 +347,9 @@ class StorageBase(object):
def impl_allow_empty_list(self):
return getattr(self, '_allow_empty_list', undefined)
def impl_is_unique(self):
return getattr(self, '_unique', False)
def _get_extra(self, key):
extra = self._extra
if isinstance(extra, tuple):

View File

@ -298,11 +298,11 @@ class Values(object):
def _get_validated_value(self, opt, path, validate, force_permissive,
validate_properties, setting_properties,
self_properties,
self_properties,
index=None, submulti_index=undefined,
with_meta=True,
masterlen=undefined,
check_frozen=False,
check_frozen=False,
session=None, display_warnings=True):
"""same has getitem but don't touch the cache
index is None for slave value, if value returned is not a list, just return []
@ -498,7 +498,7 @@ class Values(object):
self_properties=self_properties, session=session)
if isinstance(value, Exception):
raise value
owner = self._p_.getowner(path, owners.default, session, only_default=only_default, index=index)
if validate_meta is undefined:
if opt.impl_is_master_slaves('slave'):
@ -862,14 +862,19 @@ class Multi(list):
fake_context = context._gen_fake_values(session)
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)
if index is None:
fake_multi.extend(iterable, validate=False)
self._validate(fake_multi, fake_context, index)
else:
fake_multi[index].extend(iterable, validate=False)
self._validate(fake_multi[index], fake_context, index)
super(Multi, self).extend(iterable)
self._store()
def _validate(self, value, fake_context, force_index, submulti=False):
err = self.opt.impl_validate(value, context=fake_context,
force_index=force_index)
force_index=force_index,
multi=self)
if err:
raise err
@ -939,7 +944,8 @@ class SubMulti(Multi):
else:
err = self.opt.impl_validate(value, context=fake_context,
force_index=self._index,
force_submulti_index=force_index)
force_submulti_index=force_index,
multi=self)
if err:
raise err