add 'operator' to requirement
This commit is contained in:
parent
f522fd0d53
commit
9b78f46e9d
6 changed files with 319 additions and 128 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
Sat May 20 16:27:09 2017 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
|
* add 'operator' to requirement
|
||||||
|
|
||||||
Wed May 17 22:11:55 2017 +0200 Emmanuel Garette <egarette@cadoles.com>
|
Wed May 17 22:11:55 2017 +0200 Emmanuel Garette <egarette@cadoles.com>
|
||||||
* add 'remove' to Multi
|
* add 'remove' to Multi
|
||||||
|
|
||||||
|
|
|
@ -431,6 +431,122 @@ def test_requires_multi_disabled():
|
||||||
assert props == ['disabled']
|
assert props == ['disabled']
|
||||||
|
|
||||||
|
|
||||||
|
def test_requires_multi_disabled_new_format():
|
||||||
|
a = BoolOption('activate_service', '')
|
||||||
|
b = IntOption('num_service', '')
|
||||||
|
c = IPOption('ip_address_service', '',
|
||||||
|
requires=[{'expected': [{'option': a, 'value': True}, {'option': b, 'value': 1}], 'action': 'disabled'}])
|
||||||
|
od = OptionDescription('service', '', [a, b, c])
|
||||||
|
c = Config(od)
|
||||||
|
c.read_write()
|
||||||
|
|
||||||
|
c.ip_address_service
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['disabled']
|
||||||
|
|
||||||
|
c.activate_service = False
|
||||||
|
c.ip_address_service
|
||||||
|
|
||||||
|
c.num_service = 1
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['disabled']
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['disabled']
|
||||||
|
|
||||||
|
|
||||||
|
def test_requires_multi_disabled_new_format_and():
|
||||||
|
a = BoolOption('activate_service', '')
|
||||||
|
b = IntOption('num_service', '')
|
||||||
|
c = IPOption('ip_address_service', '',
|
||||||
|
requires=[{'expected': [{'option': a, 'value': True}, {'option': b, 'value': 1}], 'action': 'disabled', 'operator': 'and'}])
|
||||||
|
od = OptionDescription('service', '', [a, b, c])
|
||||||
|
c = Config(od)
|
||||||
|
c.read_write()
|
||||||
|
|
||||||
|
c.ip_address_service
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == []
|
||||||
|
|
||||||
|
c.activate_service = False
|
||||||
|
c.ip_address_service
|
||||||
|
|
||||||
|
c.num_service = 1
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == []
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['disabled']
|
||||||
|
|
||||||
|
|
||||||
|
def test_requires_multi_disabled_new_format_and_2():
|
||||||
|
a = BoolOption('activate_service', '')
|
||||||
|
b = IntOption('num_service', '')
|
||||||
|
c = IPOption('ip_address_service', '',
|
||||||
|
requires=[{'expected': [{'option': a, 'value': True}, {'option': b, 'value': 1}], 'action': 'disabled', 'operator': 'and'},
|
||||||
|
{'expected': [{'option': a, 'value': False}, {'option': b, 'value': 1}], 'action': 'expert'}])
|
||||||
|
od = OptionDescription('service', '', [a, b, c])
|
||||||
|
c = Config(od)
|
||||||
|
c.cfgimpl_get_settings().append('expert')
|
||||||
|
c.read_write()
|
||||||
|
c.ip_address_service
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == []
|
||||||
|
|
||||||
|
c.activate_service = False
|
||||||
|
c.num_service = 1
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['expert']
|
||||||
|
|
||||||
|
c.activate_service = True
|
||||||
|
props = []
|
||||||
|
try:
|
||||||
|
c.ip_address_service
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
props = err.proptype
|
||||||
|
assert props == ['disabled', 'expert']
|
||||||
|
|
||||||
|
|
||||||
def test_requires_multi_disabled_inverse():
|
def test_requires_multi_disabled_inverse():
|
||||||
a = BoolOption('activate_service', '')
|
a = BoolOption('activate_service', '')
|
||||||
b = IntOption('num_service', '')
|
b = IntOption('num_service', '')
|
||||||
|
|
|
@ -78,7 +78,9 @@ def _diff_opt(opt1, opt2):
|
||||||
if val1 == val2 == []:
|
if val1 == val2 == []:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
assert val1[0][0][0]._name == val2[0][0][0]._name
|
for idx, req in enumerate(val1[0][0][0]):
|
||||||
|
assert val1[0][0][0][idx][0]._name == val2[0][0][0][idx][0]._name
|
||||||
|
assert val1[0][0][0][idx][1] == val2[0][0][0][idx][1]
|
||||||
assert val1[0][0][1:] == val2[0][0][1:]
|
assert val1[0][0][1:] == val2[0][0][1:]
|
||||||
elif attr == '_opt':
|
elif attr == '_opt':
|
||||||
assert val1._name == val2._name
|
assert val1._name == val2._name
|
||||||
|
|
|
@ -258,20 +258,22 @@ class BaseOption(Base):
|
||||||
new_value = []
|
new_value = []
|
||||||
for requires in _requires:
|
for requires in _requires:
|
||||||
new_requires = []
|
new_requires = []
|
||||||
|
new_req = []
|
||||||
for require in requires:
|
for require in requires:
|
||||||
|
for req in require[0]:
|
||||||
if load:
|
if load:
|
||||||
new_require = [descr.impl_get_opt_by_path(require[0])]
|
new_req.append([(descr.impl_get_opt_by_path(req[0]), req[1])])
|
||||||
else:
|
else:
|
||||||
new_require = [descr.impl_get_path_by_opt(require[0])]
|
new_req.append([(descr.impl_get_path_by_opt(req[0]), req[1])])
|
||||||
new_require.extend(require[1:])
|
new_req.extend(require[1:])
|
||||||
new_requires.append(tuple(new_require))
|
new_requires.append(tuple(new_req))
|
||||||
new_value.append(tuple(new_requires))
|
new_value.append(tuple(new_requires))
|
||||||
if load:
|
if load:
|
||||||
del(self._state_requires)
|
del(self._state_requires)
|
||||||
if new_value != []:
|
if new_value != []:
|
||||||
self._requires = new_value
|
self._requires = tuple(new_value)
|
||||||
else:
|
else:
|
||||||
self._state_requires = new_value
|
self._state_requires = tuple(new_value)
|
||||||
|
|
||||||
# serialize
|
# serialize
|
||||||
def _impl_getstate(self, descr):
|
def _impl_getstate(self, descr):
|
||||||
|
@ -932,6 +934,94 @@ def validate_requires_arg(multi, requires, name):
|
||||||
know more about
|
know more about
|
||||||
the description of the requires dictionary
|
the description of the requires dictionary
|
||||||
"""
|
"""
|
||||||
|
def get_option(require):
|
||||||
|
option = require['option']
|
||||||
|
if not isinstance(option, Option):
|
||||||
|
raise ValueError(_('malformed requirements '
|
||||||
|
'must be an option in option {0}').format(name))
|
||||||
|
if not multi and option.impl_is_multi():
|
||||||
|
raise ValueError(_('malformed requirements '
|
||||||
|
'multi option must not set '
|
||||||
|
'as requires of non multi option {0}').format(name))
|
||||||
|
return option
|
||||||
|
|
||||||
|
def _set_expected(action, inverse, transitive, same_action, option, expected, operator):
|
||||||
|
if inverse not in ret_requires[action]:
|
||||||
|
ret_requires[action][inverse] = ([(option, [expected])], action, inverse, transitive, same_action, operator)
|
||||||
|
else:
|
||||||
|
for exp in ret_requires[action][inverse][0]:
|
||||||
|
if exp[0] == option:
|
||||||
|
exp[1].append(expected)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
ret_requires[action][inverse][0].append((option, [expected]))
|
||||||
|
|
||||||
|
def set_expected(require, ret_requires):
|
||||||
|
expected = require['expected']
|
||||||
|
inverse = get_inverse(require)
|
||||||
|
transitive = get_transitive(require)
|
||||||
|
same_action = get_sameaction(require)
|
||||||
|
operator = get_operator(require)
|
||||||
|
if isinstance(expected, list):
|
||||||
|
for exp in expected:
|
||||||
|
if exp.keys() != ['option', 'value']:
|
||||||
|
raise ValueError(_('malformed requirements expected must have '
|
||||||
|
'option and value for option {0}').format(name))
|
||||||
|
option = exp['option']
|
||||||
|
if option is not None:
|
||||||
|
err = option._validate(exp['value'])
|
||||||
|
if err:
|
||||||
|
raise ValueError(_('malformed requirements expected value '
|
||||||
|
'must be valid for option {0}'
|
||||||
|
': {1}').format(name, err))
|
||||||
|
_set_expected(action, inverse, transitive, same_action, option, exp['value'], operator)
|
||||||
|
else:
|
||||||
|
option = get_option(require)
|
||||||
|
if expected is not None:
|
||||||
|
err = option._validate(expected)
|
||||||
|
if err:
|
||||||
|
raise ValueError(_('malformed requirements expected value '
|
||||||
|
'must be valid for option {0}'
|
||||||
|
': {1}').format(name, err))
|
||||||
|
_set_expected(action, inverse, transitive, same_action, option, expected, operator)
|
||||||
|
|
||||||
|
def get_action(require):
|
||||||
|
action = require['action']
|
||||||
|
if action == 'force_store_value':
|
||||||
|
raise ValueError(_("malformed requirements for option: {0}"
|
||||||
|
" action cannot be force_store_value"
|
||||||
|
).format(name))
|
||||||
|
return action
|
||||||
|
|
||||||
|
def get_inverse(require):
|
||||||
|
inverse = require.get('inverse', False)
|
||||||
|
if inverse not in [True, False]:
|
||||||
|
raise ValueError(_('malformed requirements for option: {0}'
|
||||||
|
' inverse must be boolean'))
|
||||||
|
return inverse
|
||||||
|
|
||||||
|
def get_transitive(require):
|
||||||
|
transitive = require.get('transitive', True)
|
||||||
|
if transitive not in [True, False]:
|
||||||
|
raise ValueError(_('malformed requirements for option: {0}'
|
||||||
|
' transitive must be boolean'))
|
||||||
|
return transitive
|
||||||
|
|
||||||
|
def get_sameaction(require):
|
||||||
|
same_action = require.get('same_action', True)
|
||||||
|
if same_action not in [True, False]:
|
||||||
|
raise ValueError(_('malformed requirements for option: {0}'
|
||||||
|
' same_action must be boolean'))
|
||||||
|
return same_action
|
||||||
|
|
||||||
|
def get_operator(require):
|
||||||
|
operator = require.get('operator', 'or')
|
||||||
|
if operator not in ['and', 'or']:
|
||||||
|
raise ValueError(_('malformed requirements for option: {0}'
|
||||||
|
' operator must be "or" or "and"'))
|
||||||
|
return operator
|
||||||
|
|
||||||
|
|
||||||
ret_requires = {}
|
ret_requires = {}
|
||||||
config_action = set()
|
config_action = set()
|
||||||
|
|
||||||
|
@ -942,7 +1032,7 @@ def validate_requires_arg(multi, requires, name):
|
||||||
raise ValueError(_("malformed requirements type for option:"
|
raise ValueError(_("malformed requirements type for option:"
|
||||||
" {0}, must be a dict").format(name))
|
" {0}, must be a dict").format(name))
|
||||||
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
|
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
|
||||||
'same_action')
|
'same_action', 'operator')
|
||||||
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
|
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
|
||||||
if unknown_keys != frozenset():
|
if unknown_keys != frozenset():
|
||||||
raise ValueError(_('malformed requirements for option: {0}'
|
raise ValueError(_('malformed requirements for option: {0}'
|
||||||
|
@ -951,62 +1041,25 @@ def validate_requires_arg(multi, requires, name):
|
||||||
unknown_keys,
|
unknown_keys,
|
||||||
valid_keys))
|
valid_keys))
|
||||||
# prepare all attributes
|
# prepare all attributes
|
||||||
if 'option' not in require or 'expected' not in require or \
|
if not ('expected' in require and isinstance(require['expected'], list)) and \
|
||||||
|
not ('option' in require and 'expected' in require) or \
|
||||||
'action' not in require:
|
'action' not in require:
|
||||||
raise ValueError(_("malformed requirements for option: {0}"
|
raise ValueError(_("malformed requirements for option: {0}"
|
||||||
" require must have option, expected and"
|
" require must have option, expected and"
|
||||||
" action keys").format(name))
|
" action keys").format(name))
|
||||||
option = require['option']
|
action = get_action(require)
|
||||||
expected = require['expected']
|
|
||||||
action = require['action']
|
|
||||||
if action == 'force_store_value':
|
|
||||||
raise ValueError(_("malformed requirements for option: {0}"
|
|
||||||
" action cannot be force_store_value"
|
|
||||||
).format(name))
|
|
||||||
inverse = require.get('inverse', False)
|
|
||||||
if inverse not in [True, False]:
|
|
||||||
raise ValueError(_('malformed requirements for option: {0}'
|
|
||||||
' inverse must be boolean'))
|
|
||||||
transitive = require.get('transitive', True)
|
|
||||||
if transitive not in [True, False]:
|
|
||||||
raise ValueError(_('malformed requirements for option: {0}'
|
|
||||||
' transitive must be boolean'))
|
|
||||||
same_action = require.get('same_action', True)
|
|
||||||
if same_action not in [True, False]:
|
|
||||||
raise ValueError(_('malformed requirements for option: {0}'
|
|
||||||
' same_action must be boolean'))
|
|
||||||
|
|
||||||
if not isinstance(option, Option):
|
|
||||||
raise ValueError(_('malformed requirements '
|
|
||||||
'must be an option in option {0}').format(name))
|
|
||||||
if not multi and option.impl_is_multi():
|
|
||||||
raise ValueError(_('malformed requirements '
|
|
||||||
'multi option must not set '
|
|
||||||
'as requires of non multi option {0}').format(name))
|
|
||||||
if expected is not None:
|
|
||||||
err = option._validate(expected)
|
|
||||||
if err:
|
|
||||||
raise ValueError(_('malformed requirements expected value '
|
|
||||||
'must be valid for option {0}'
|
|
||||||
': {1}').format(name, err))
|
|
||||||
config_action.add(action)
|
config_action.add(action)
|
||||||
if action not in ret_requires:
|
if action not in ret_requires:
|
||||||
ret_requires[action] = {}
|
ret_requires[action] = {}
|
||||||
if option not in ret_requires[action]:
|
set_expected(require, ret_requires)
|
||||||
ret_requires[action][option] = {}
|
|
||||||
if inverse not in ret_requires[action][option]:
|
|
||||||
ret_requires[action][option][inverse] = (option, [expected], action,
|
|
||||||
inverse, transitive, same_action)
|
|
||||||
else:
|
|
||||||
ret_requires[action][option][inverse][1].append(expected)
|
|
||||||
# transform dict to tuple
|
# transform dict to tuple
|
||||||
ret = []
|
ret = []
|
||||||
for opt_requires in ret_requires.values():
|
for requires in ret_requires.values():
|
||||||
ret_action = []
|
ret_action = []
|
||||||
for requires in opt_requires.values():
|
|
||||||
for require in requires.values():
|
for require in requires.values():
|
||||||
ret_action.append((require[0], tuple(require[1]), require[2],
|
ret_action.append((tuple(require[0]), require[1],
|
||||||
require[3], require[4], require[5]))
|
require[2], require[3], require[4], require[5]))
|
||||||
ret.append(tuple(ret_action))
|
ret.append(tuple(ret_action))
|
||||||
return frozenset(config_action), tuple(ret)
|
return frozenset(config_action), tuple(ret)
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
|
||||||
# * option in require must be a master or a slave
|
# * option in require must be a master or a slave
|
||||||
# * current option must be a slave (and only a slave)
|
# * current option must be a slave (and only a slave)
|
||||||
# * option in require and current option must be in same master/slaves
|
# * option in require and current option must be in same master/slaves
|
||||||
require_opt = require[0]
|
for require_opt, values in require[0]:
|
||||||
if require_opt.impl_is_multi():
|
if require_opt.impl_is_multi():
|
||||||
if is_slave is None:
|
if is_slave is None:
|
||||||
is_slave = option.impl_is_master_slaves('slave')
|
is_slave = option.impl_is_master_slaves('slave')
|
||||||
|
|
|
@ -644,8 +644,11 @@ class Settings(object):
|
||||||
all_properties = None
|
all_properties = None
|
||||||
for requires in current_requires:
|
for requires in current_requires:
|
||||||
for require in requires:
|
for require in requires:
|
||||||
option, expected, action, inverse, \
|
exps, action, inverse, \
|
||||||
transitive, same_action = require
|
transitive, same_action, operator = require
|
||||||
|
breaked = False
|
||||||
|
for exp in exps:
|
||||||
|
option, expected = exp
|
||||||
reqpath = option.impl_getpath(context)
|
reqpath = option.impl_getpath(context)
|
||||||
if reqpath == path or reqpath.startswith(path + '.'): # pragma: optional cover
|
if reqpath == path or reqpath.startswith(path + '.'): # pragma: optional cover
|
||||||
raise RequirementError(_("malformed requirements "
|
raise RequirementError(_("malformed requirements "
|
||||||
|
@ -654,6 +657,8 @@ class Settings(object):
|
||||||
"'{1}'").format(path, reqpath))
|
"'{1}'").format(path, reqpath))
|
||||||
if option.impl_is_multi():
|
if option.impl_is_multi():
|
||||||
if index is None:
|
if index is None:
|
||||||
|
# multi is allowed only for slaves
|
||||||
|
# so do not calculated requires if no index
|
||||||
continue
|
continue
|
||||||
idx = index
|
idx = index
|
||||||
else:
|
else:
|
||||||
|
@ -668,7 +673,7 @@ class Settings(object):
|
||||||
all_properties = []
|
all_properties = []
|
||||||
for requires in opt.impl_getrequires():
|
for requires in opt.impl_getrequires():
|
||||||
for require in requires:
|
for require in requires:
|
||||||
all_properties.append(require[2])
|
all_properties.append(require[1])
|
||||||
if not set(value.proptype) - set(all_properties):
|
if not set(value.proptype) - set(all_properties):
|
||||||
continue
|
continue
|
||||||
properties = value.proptype
|
properties = value.proptype
|
||||||
|
@ -693,18 +698,30 @@ class Settings(object):
|
||||||
orig_value = value
|
orig_value = value
|
||||||
if (not inverse and value in expected or
|
if (not inverse and value in expected or
|
||||||
inverse and value not in expected):
|
inverse and value not in expected):
|
||||||
|
if operator != 'and':
|
||||||
if debug:
|
if debug:
|
||||||
if isinstance(orig_value, PropertiesOptionError):
|
if isinstance(orig_value, PropertiesOptionError):
|
||||||
for act, msg in orig_value._settings.apply_requires(**orig_value._datas).items():
|
for msg in orig_value._settings.apply_requires(**orig_value._datas).values():
|
||||||
calc_properties.setdefault(action, []).extend(msg)
|
calc_properties.setdefault(action, []).extend(msg)
|
||||||
else:
|
else:
|
||||||
if not inverse:
|
if not inverse:
|
||||||
msg = _('the value of "{0}" is "{1}"')
|
msg = _('the value of "{0}" is "{1}"')
|
||||||
else:
|
else:
|
||||||
msg = _('the value of "{0}" is not "{1}"')
|
msg = _('the value of "{0}" is not "{1}"')
|
||||||
calc_properties.setdefault(action, []).append(msg.format(option.impl_get_display_name(), display_list(expected, 'or')))
|
calc_properties.setdefault(action, []).append(
|
||||||
|
msg.format(option.impl_get_display_name(),
|
||||||
|
display_list(expected, 'or')))
|
||||||
else:
|
else:
|
||||||
calc_properties.add(action)
|
calc_properties.add(action)
|
||||||
|
breaked = True
|
||||||
|
break
|
||||||
|
elif operator == 'and':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if operator == 'and':
|
||||||
|
calc_properties.add(action)
|
||||||
|
continue
|
||||||
|
if breaked:
|
||||||
break
|
break
|
||||||
return calc_properties
|
return calc_properties
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue