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>
|
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>
|
Wed Oct 12 21:55:53 2016 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
* consistency is now check "not_equal" if one option has
|
* consistency is now check "not_equal" if one option has
|
||||||
|
|
|
@ -24,6 +24,25 @@ def test_multi():
|
||||||
raises(ConfigError, "multi._getcontext()")
|
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():
|
def test_multi_none():
|
||||||
s = StrOption('str', '', multi=True)
|
s = StrOption('str', '', multi=True)
|
||||||
o = OptionDescription('od', '', [s])
|
o = OptionDescription('od', '', [s])
|
||||||
|
|
|
@ -4,7 +4,7 @@ do_autopath()
|
||||||
|
|
||||||
from tiramisu.setting import groups, owners
|
from tiramisu.setting import groups, owners
|
||||||
from tiramisu.config import Config
|
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.value import SubMulti, Multi
|
||||||
from tiramisu.error import SlaveError
|
from tiramisu.error import SlaveError
|
||||||
|
|
||||||
|
@ -663,3 +663,19 @@ def test_callback_submulti():
|
||||||
assert cfg.getowner(multi2) == owners.default
|
assert cfg.getowner(multi2) == owners.default
|
||||||
assert cfg.multi == [['val']]
|
assert cfg.multi == [['val']]
|
||||||
assert cfg.multi2 == [['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()
|
__slots__ = tuple()
|
||||||
|
|
||||||
def __init__(self, name, doc, default=None, default_multi=None,
|
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,
|
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=undefined, session=None):
|
allow_empty_list=undefined, session=None):
|
||||||
|
@ -122,6 +122,10 @@ class Base(StorageBase):
|
||||||
_multi = submulti
|
_multi = submulti
|
||||||
else:
|
else:
|
||||||
raise ValueError(_('invalid multi value'))
|
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:
|
if requires is not None:
|
||||||
calc_properties, requires = validate_requires_arg(is_multi,
|
calc_properties, requires = validate_requires_arg(is_multi,
|
||||||
requires, name)
|
requires, name)
|
||||||
|
@ -149,7 +153,7 @@ class Base(StorageBase):
|
||||||
session = self.getsession()
|
session = self.getsession()
|
||||||
StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
|
StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
|
||||||
calc_properties, requires, properties,
|
calc_properties, requires, properties,
|
||||||
allow_empty_list, session=session)
|
allow_empty_list, unique, session=session)
|
||||||
if multi is not False and default is None:
|
if multi is not False and default is None:
|
||||||
default = []
|
default = []
|
||||||
err = self.impl_validate(default, is_multi=is_multi)
|
err = self.impl_validate(default, is_multi=is_multi)
|
||||||
|
@ -175,7 +179,7 @@ class Base(StorageBase):
|
||||||
" yet for option {0}").format(
|
" yet for option {0}").format(
|
||||||
self.impl_getname()))
|
self.impl_getname()))
|
||||||
if not _init and self.impl_get_callback()[0] is not None:
|
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()))
|
"cannot set another one's").format(self.impl_getname()))
|
||||||
self._validate_callback(callback, callback_params)
|
self._validate_callback(callback, callback_params)
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
|
@ -415,9 +419,8 @@ class Option(OnlyOption):
|
||||||
all_cons_opts = []
|
all_cons_opts = []
|
||||||
val_consistencies = True
|
val_consistencies = True
|
||||||
for opt in opts:
|
for opt in opts:
|
||||||
is_multi = opt.impl_is_multi() and not opt.impl_is_master_slaves()
|
if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
|
||||||
if not is_multi and ((isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
|
option == opt:
|
||||||
option == opt):
|
|
||||||
# option is current option
|
# option is current option
|
||||||
# we have already value, so use it
|
# we have already value, so use it
|
||||||
all_cons_vals.append(value)
|
all_cons_vals.append(value)
|
||||||
|
@ -425,6 +428,7 @@ class Option(OnlyOption):
|
||||||
else:
|
else:
|
||||||
#if context, calculate value, otherwise get default value
|
#if context, calculate value, otherwise get default value
|
||||||
path = None
|
path = None
|
||||||
|
is_multi = opt.impl_is_multi() and not opt.impl_is_master_slaves()
|
||||||
if context is not undefined:
|
if context is not undefined:
|
||||||
if isinstance(opt, DynSymLinkOption):
|
if isinstance(opt, DynSymLinkOption):
|
||||||
path = opt.impl_getpath(context)
|
path = opt.impl_getpath(context)
|
||||||
|
@ -470,7 +474,7 @@ class Option(OnlyOption):
|
||||||
def impl_validate(self, value, context=undefined, validate=True,
|
def impl_validate(self, value, context=undefined, validate=True,
|
||||||
force_index=None, force_submulti_index=None,
|
force_index=None, force_submulti_index=None,
|
||||||
current_opt=undefined, is_multi=None,
|
current_opt=undefined, is_multi=None,
|
||||||
display_warnings=True):
|
display_warnings=True, multi=None):
|
||||||
"""
|
"""
|
||||||
:param value: the option's value
|
:param value: the option's value
|
||||||
:param context: Config's context
|
:param context: Config's context
|
||||||
|
@ -489,6 +493,13 @@ class Option(OnlyOption):
|
||||||
if current_opt is undefined:
|
if current_opt is undefined:
|
||||||
current_opt = self
|
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):
|
def calculation_validator(val):
|
||||||
validator, validator_params = self.impl_get_validator()
|
validator, validator_params = self.impl_get_validator()
|
||||||
if validator is not None:
|
if validator is not None:
|
||||||
|
@ -589,41 +600,58 @@ class Option(OnlyOption):
|
||||||
|
|
||||||
if is_multi is None:
|
if is_multi is None:
|
||||||
is_multi = self.impl_is_multi()
|
is_multi = self.impl_is_multi()
|
||||||
|
|
||||||
if not is_multi:
|
if not is_multi:
|
||||||
return do_validation(value, None, None)
|
return do_validation(value, None, None)
|
||||||
elif force_index is not None:
|
elif force_index is not None:
|
||||||
if self.impl_is_submulti() and force_submulti_index is 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
|
if not isinstance(value, list): # pragma: optional cover
|
||||||
raise ValueError(_("invalid value {0} for option {1} which"
|
return ValueError(_('invalid value "{0}" for "{1}" which'
|
||||||
" must be a list").format(
|
' must be a list').format(
|
||||||
value, self.impl_getname()))
|
value, self.impl_get_display_name()))
|
||||||
for idx, val in enumerate(value):
|
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)
|
err = do_validation(val, force_index, idx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
else:
|
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)
|
return do_validation(value, force_index, force_submulti_index)
|
||||||
elif not isinstance(value, list): # pragma: optional cover
|
elif not isinstance(value, list): # pragma: optional cover
|
||||||
return ValueError(_("invalid value {0} for option {1} which "
|
return ValueError(_('invalid value "{0}" for "{1}" which '
|
||||||
"must be a list").format(value,
|
'must be a list').format(value,
|
||||||
self.impl_getname()))
|
self.impl_getname()))
|
||||||
elif self.impl_is_submulti() and force_submulti_index is None:
|
elif self.impl_is_submulti() and force_submulti_index is None:
|
||||||
for idx, val in enumerate(value):
|
for idx, val in enumerate(value):
|
||||||
|
err = _is_not_unique(val)
|
||||||
|
if err:
|
||||||
|
return err
|
||||||
if not isinstance(val, list): # pragma: optional cover
|
if not isinstance(val, list): # pragma: optional cover
|
||||||
return ValueError(_("invalid value {0} for option {1} "
|
return ValueError(_('invalid value "{0}" for "{1}" '
|
||||||
"which must be a list of list"
|
'which must be a list of list'
|
||||||
"").format(value,
|
'').format(val,
|
||||||
self.impl_getname()))
|
self.impl_getname()))
|
||||||
for slave_idx, slave_val in enumerate(val):
|
for slave_idx, slave_val in enumerate(val):
|
||||||
err = do_validation(slave_val, idx, slave_idx)
|
err = do_validation(slave_val, idx, slave_idx)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
else:
|
else:
|
||||||
|
err = _is_not_unique(value)
|
||||||
|
if err:
|
||||||
|
return err
|
||||||
for idx, val in enumerate(value):
|
for idx, val in enumerate(value):
|
||||||
err = do_validation(val, idx, force_submulti_index)
|
err = do_validation(val, idx, force_submulti_index)
|
||||||
if err:
|
if err:
|
||||||
return err
|
return err
|
||||||
else:
|
|
||||||
return self._valid_consistency(current_opt, None, context,
|
return self._valid_consistency(current_opt, None, context,
|
||||||
None, None)
|
None, None)
|
||||||
|
|
||||||
|
@ -717,6 +745,10 @@ class Option(OnlyOption):
|
||||||
if err:
|
if err:
|
||||||
self._del_consistency()
|
self._del_consistency()
|
||||||
raise err
|
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
|
#consistency could generate warnings or errors
|
||||||
self._set_has_dependency()
|
self._set_has_dependency()
|
||||||
|
|
||||||
|
@ -948,7 +980,7 @@ class SymLinkOption(OnlyOption):
|
||||||
session = self.getsession()
|
session = self.getsession()
|
||||||
super(Base, self).__init__(name, undefined, undefined, undefined,
|
super(Base, self).__init__(name, undefined, undefined, undefined,
|
||||||
undefined, undefined, undefined, undefined,
|
undefined, undefined, undefined, undefined,
|
||||||
undefined, opt, session=session)
|
undefined, undefined, opt=opt, session=session)
|
||||||
opt._set_has_dependency()
|
opt._set_has_dependency()
|
||||||
self.commit(session)
|
self.commit(session)
|
||||||
|
|
||||||
|
@ -1030,13 +1062,14 @@ class DynSymLinkOption(object):
|
||||||
|
|
||||||
def impl_validate(self, value, context=undefined, validate=True,
|
def impl_validate(self, value, context=undefined, validate=True,
|
||||||
force_index=None, force_submulti_index=None, is_multi=None,
|
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,
|
return self._impl_getopt().impl_validate(value, context, validate,
|
||||||
force_index,
|
force_index,
|
||||||
force_submulti_index,
|
force_submulti_index,
|
||||||
current_opt=self,
|
current_opt=self,
|
||||||
is_multi=is_multi,
|
is_multi=is_multi,
|
||||||
display_warnings=display_warnings)
|
display_warnings=display_warnings,
|
||||||
|
multi=multi)
|
||||||
|
|
||||||
def impl_is_dynsymlinkoption(self):
|
def impl_is_dynsymlinkoption(self):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -310,7 +310,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
|
||||||
if isinstance(values, Exception):
|
if isinstance(values, Exception):
|
||||||
raise values
|
raise values
|
||||||
if len(values) > len(set(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:
|
for val in values:
|
||||||
if not isinstance(val, str) or re.match(name_regexp, val) is None:
|
if not isinstance(val, str) or re.match(name_regexp, val) is None:
|
||||||
raise ValueError(_("invalid suffix: {0} for option").format(val))
|
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):
|
class StorageBase(object):
|
||||||
__slots__ = ('_name',
|
__slots__ = ('_name',
|
||||||
'_informations',
|
'_informations',
|
||||||
'_multi',
|
|
||||||
'_extra',
|
'_extra',
|
||||||
'_warnings_only',
|
'_warnings_only',
|
||||||
'_allow_empty_list',
|
'_allow_empty_list',
|
||||||
|
#multi
|
||||||
|
'_multi',
|
||||||
|
'_unique',
|
||||||
#value
|
#value
|
||||||
'_default',
|
'_default',
|
||||||
'_default_multi',
|
'_default_multi',
|
||||||
|
@ -66,7 +68,7 @@ class StorageBase(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
|
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):
|
session=None):
|
||||||
_setattr = object.__setattr__
|
_setattr = object.__setattr__
|
||||||
_setattr(self, '_name', name)
|
_setattr(self, '_name', name)
|
||||||
|
@ -89,6 +91,8 @@ class StorageBase(object):
|
||||||
_setattr(self, '_opt', opt)
|
_setattr(self, '_opt', opt)
|
||||||
if allow_empty_list is not undefined:
|
if allow_empty_list is not undefined:
|
||||||
_setattr(self, '_allow_empty_list', allow_empty_list)
|
_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):
|
def _set_default_values(self, default, default_multi, is_multi):
|
||||||
_setattr = object.__setattr__
|
_setattr = object.__setattr__
|
||||||
|
@ -343,6 +347,9 @@ class StorageBase(object):
|
||||||
def impl_allow_empty_list(self):
|
def impl_allow_empty_list(self):
|
||||||
return getattr(self, '_allow_empty_list', undefined)
|
return getattr(self, '_allow_empty_list', undefined)
|
||||||
|
|
||||||
|
def impl_is_unique(self):
|
||||||
|
return getattr(self, '_unique', False)
|
||||||
|
|
||||||
def _get_extra(self, key):
|
def _get_extra(self, key):
|
||||||
extra = self._extra
|
extra = self._extra
|
||||||
if isinstance(extra, tuple):
|
if isinstance(extra, tuple):
|
||||||
|
|
|
@ -862,14 +862,19 @@ class Multi(list):
|
||||||
fake_context = context._gen_fake_values(session)
|
fake_context = context._gen_fake_values(session)
|
||||||
fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
|
fake_multi = fake_context.cfgimpl_get_values()._get_cached_value(
|
||||||
self.opt, path=self.path, validate=False)
|
self.opt, path=self.path, validate=False)
|
||||||
|
if index is None:
|
||||||
fake_multi.extend(iterable, validate=False)
|
fake_multi.extend(iterable, validate=False)
|
||||||
self._validate(iterable, fake_context, index)
|
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)
|
super(Multi, self).extend(iterable)
|
||||||
self._store()
|
self._store()
|
||||||
|
|
||||||
def _validate(self, value, fake_context, force_index, submulti=False):
|
def _validate(self, value, fake_context, force_index, submulti=False):
|
||||||
err = self.opt.impl_validate(value, context=fake_context,
|
err = self.opt.impl_validate(value, context=fake_context,
|
||||||
force_index=force_index)
|
force_index=force_index,
|
||||||
|
multi=self)
|
||||||
if err:
|
if err:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
@ -939,7 +944,8 @@ class SubMulti(Multi):
|
||||||
else:
|
else:
|
||||||
err = self.opt.impl_validate(value, context=fake_context,
|
err = self.opt.impl_validate(value, context=fake_context,
|
||||||
force_index=self._index,
|
force_index=self._index,
|
||||||
force_submulti_index=force_index)
|
force_submulti_index=force_index,
|
||||||
|
multi=self)
|
||||||
if err:
|
if err:
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue