add unique parameter to option
This commit is contained in:
parent
fc36f674eb
commit
42d830687d
7 changed files with 116 additions and 33 deletions
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue