requirement can have callback

This commit is contained in:
2019-03-13 08:49:18 +01:00
parent 05abe76932
commit cab8dae15a
11 changed files with 712 additions and 176 deletions

View File

@ -465,8 +465,9 @@ class _TiramisuOptionValueOption:
if isinstance(value, list):
while undefined in value:
idx = value.index(undefined)
value[idx] = values.getdefaultvalue(self._option_bag,
force_index=idx)
soption_bag = self._option_bag.copy()
soption_bag.index = idx
value[idx] = values.getdefaultvalue(soption_bag)
elif value == undefined:
value = values.getdefaultvalue(self._option_bag)
self._subconfig.setattr(value,

View File

@ -90,11 +90,11 @@ def manager_callback(callbk: Union[ParamOption, ParamValue],
return value[index]
return value
except PropertiesOptionError as err:
# raise because must not add value None in carry_out_calculation
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if callbk.notraisepropertyerror:
raise err
raise ConfigError(_('unable to carry out a calculation for "{}"'
', {}').format(option.impl_get_display_name(), err))
', {}').format(option.impl_get_display_name(), err), err)
def carry_out_calculation(option,

View File

@ -116,7 +116,7 @@ class PropertiesOptionError(AttributeError):
self._name,
prop_msg,
msg))
del self._requires, self._opt_type, self._name, self._option_bag
del self._requires, self._opt_type, self._name
del self._settings, self._orig_opt
return self.msg
@ -127,7 +127,11 @@ class ConfigError(Exception):
"""attempt to change an option's owner without a value
or in case of `_cfgimpl_descr` is None
or if a calculation cannot be carried out"""
pass
def __init__(self,
exp,
ori_err=None):
super().__init__(exp)
self.ori_err = ori_err
class ConflictError(Exception):

View File

@ -49,9 +49,12 @@ class Param:
class ParamOption(Param):
__slots__ = ('option', 'notraisepropertyerror')
def __init__(self, option, notraisepropertyerror=False):
if not hasattr(option, 'impl_is_symlinkoption'):
__slots__ = ('option',
'notraisepropertyerror')
def __init__(self,
option: 'Option',
notraisepropertyerror: bool=False) -> None:
if __debug__ and not hasattr(option, 'impl_is_symlinkoption'):
raise ValueError(_('paramoption needs an option not {}').format(type(option)))
if option.impl_is_symlinkoption():
cur_opt = option.impl_getopt()
@ -95,6 +98,7 @@ def calc_value(*args: List[Any],
join: Optional[str]=None,
min_args_len: Optional[int]=None,
operator: Optional[str]=None,
index: Optional[int]=None,
**kwargs) -> Any:
"""calculate value
:param multi: value returns must be a list of value
@ -181,7 +185,6 @@ def calc_value(*args: List[Any],
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
* you want to join two values with '.'
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
@ -305,6 +308,11 @@ def calc_value(*args: List[Any],
value = None
else:
value = value[0]
if isinstance(value, list) and index is not None:
if len(value) > index:
value = value[index]
else:
value = None
elif None in value and not allow_none:
value = []
elif remove_duplicate_value:

View File

@ -170,7 +170,7 @@ class Base:
param.option._add_dependency(self)
if type_ == 'validator':
self._has_dependency = True
is_multi = self.impl_is_dynoptiondescription() or self.impl_is_multi()
is_multi = self.impl_is_optiondescription() or self.impl_is_multi()
func_args, func_kwargs, func_positional, func_keyword = self._get_function_args(calculator)
calculator_args, calculator_kwargs = self._get_parameters_args(calculator_params, add_value)
# remove knowned kwargs
@ -253,13 +253,14 @@ class Base:
def _impl_set_callback(self,
callback: Callable,
callback_params: Optional[Params]=None) -> None:
if callback is None and callback_params is not None:
raise ValueError(_("params defined for a callback function but "
"no callback defined"
' yet for option "{0}"').format(
self.impl_getname()))
self._validate_calculator(callback,
callback_params)
if __debug__:
if callback is None and callback_params is not None:
raise ValueError(_("params defined for a callback function but "
"no callback defined"
' yet for option "{0}"').format(
self.impl_getname()))
self._validate_calculator(callback,
callback_params)
if callback is not None:
callback_params = self._build_calculator_params(callback,
callback_params,
@ -443,17 +444,25 @@ def validate_requires_arg(new_option: BaseOption,
the description of the requires dictionary
"""
def get_option(require):
option = require['option']
if option == 'self':
option = new_option
if not isinstance(option, BaseOption):
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))
option._add_dependency(new_option)
if 'option' in require:
option = require['option']
if option == 'self':
option = new_option
if __debug__:
if not isinstance(option, BaseOption):
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))
option._add_dependency(new_option)
else:
callback = require['callback']
callback_params = new_option._build_calculator_params(callback,
require.get('callback_params'),
'callback')
option = (callback, callback_params)
return option
def _set_expected(action,
@ -482,11 +491,11 @@ def validate_requires_arg(new_option: BaseOption,
operator = get_operator(require)
if isinstance(expected, list):
for exp in expected:
if set(exp.keys()) != {'option', 'value'}:
if __debug__ and set(exp.keys()) != {'option', 'value'}:
raise ValueError(_('malformed requirements expected must have '
'option and value for option {0}').format(name))
option = get_option(exp)
if option is not None:
if __debug__:
try:
option._validate(exp['value'], undefined)
except ValueError as err:
@ -502,7 +511,7 @@ def validate_requires_arg(new_option: BaseOption,
operator)
else:
option = get_option(require)
if expected is not None:
if __debug__ and not isinstance(option, tuple) and expected is not None:
try:
option._validate(expected, undefined)
except ValueError as err:
@ -560,25 +569,29 @@ def validate_requires_arg(new_option: BaseOption,
# start parsing all requires given by user (has dict)
# transforme it to a tuple
for require in requires:
if not isinstance(require, dict):
raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
'same_action', 'operator')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset():
raise ValueError(_('malformed requirements for option: {0}'
' unknown keys {1}, must only '
'{2}').format(name,
unknown_keys,
valid_keys))
# prepare all attributes
if not ('expected' in require and isinstance(require['expected'], list)) and \
not ('option' in require and 'expected' in require) or \
'action' not in require:
raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and"
" action keys").format(name))
if __debug__:
if not isinstance(require, dict):
raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
'same_action', 'operator', 'callback', 'callback_params')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset():
raise ValueError(_('malformed requirements for option: {0}'
' unknown keys {1}, must only '
'{2}').format(name,
unknown_keys,
valid_keys))
# {'expected': ..., 'option': ..., 'action': ...}
# {'expected': [{'option': ..., 'value': ...}, ...}], 'action': ...}
# {'expected': ..., 'callback': ..., 'action': ...}
if not 'expected' in require or not 'action' in require or \
not (isinstance(require['expected'], list) or \
'option' in require or \
'callback' in require):
raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and"
" action keys").format(name))
action = get_action(require)
config_action.add(action)
if action not in ret_requires:

View File

@ -109,7 +109,7 @@ class Leadership(OptionDescription):
for requires_ in getattr(self, '_requires', ()):
for require in requires_:
for require_opt, values in require[0]:
if require_opt.impl_is_multi() and require_opt.impl_get_leadership():
if not isinstance(require_opt, tuple) and require_opt.impl_is_multi() and require_opt.impl_get_leadership():
raise ValueError(_('malformed requirements option "{0}" '
'must not be in follower for "{1}"').format(
require_opt.impl_getname(),

View File

@ -139,7 +139,7 @@ class CacheOptionDescription(BaseOption):
# * current option must be a follower (and only a follower)
# * option in require and current option must be in same leadership
for require_opt, values in require[0]:
if require_opt.impl_is_multi():
if not isinstance(require_opt, tuple) and require_opt.impl_is_multi():
if is_follower is None:
is_follower = option.impl_is_follower()
if is_follower:

View File

@ -509,43 +509,59 @@ class Settings(object):
exps, action, inverse, transitive, same_action, operator = require
breaked = False
for option, expected in exps:
if option.issubdyn():
option = option.to_dynoption(option_bag.option.rootpath,
option_bag.option.impl_getsuffix())
reqpath = option.impl_getpath()
#FIXME too later!
if reqpath.startswith(option_bag.path + '.'):
raise RequirementError(_("malformed requirements "
"imbrication detected for option:"
" '{0}' with requirement on: "
"'{1}'").format(option_bag.path, reqpath))
idx = None
is_indexed = False
if option.impl_is_follower():
idx = option_bag.index
if idx is None:
if not isinstance(option, tuple):
if option.issubdyn():
option = option.to_dynoption(option_bag.option.rootpath,
option_bag.option.impl_getsuffix())
reqpath = option.impl_getpath()
if __debug__ and reqpath.startswith(option_bag.path + '.'):
# FIXME too later!
raise RequirementError(_("malformed requirements "
"imbrication detected for option:"
" '{0}' with requirement on: "
"'{1}'").format(option_bag.path, reqpath))
idx = None
is_indexed = False
if option.impl_is_follower():
idx = option_bag.index
if idx is None:
continue
elif option.impl_is_leader() and option_bag.index is None:
continue
elif option.impl_is_leader() and option_bag.index is None:
continue
elif option.impl_is_multi() and option_bag.index is not None:
is_indexed = True
config_bag = option_bag.config_bag.copy()
soption_bag = OptionBag()
soption_bag.set_option(option,
reqpath,
idx,
config_bag)
if option_bag.option == option:
soption_bag.config_bag.unrestraint()
soption_bag.config_bag.remove_validation()
soption_bag.apply_requires = False
elif option.impl_is_multi() and option_bag.index is not None:
is_indexed = True
config_bag = option_bag.config_bag.copy()
soption_bag = OptionBag()
soption_bag.set_option(option,
reqpath,
idx,
config_bag)
if option_bag.option == option:
soption_bag.config_bag.unrestraint()
soption_bag.config_bag.remove_validation()
soption_bag.apply_requires = False
else:
soption_bag.config_bag.properties = soption_bag.config_bag.true_properties
soption_bag.config_bag.set_permissive()
else:
soption_bag.config_bag.properties = soption_bag.config_bag.true_properties
soption_bag.config_bag.set_permissive()
if not option_bag.option.impl_is_optiondescription() and option_bag.option.impl_is_follower():
idx = option_bag.index
if idx is None:
continue
is_indexed = False
try:
value = context.getattr(reqpath,
soption_bag)
except PropertiesOptionError as err:
if not isinstance(option, tuple):
value = context.getattr(reqpath,
soption_bag)
else:
value = context.cfgimpl_get_values().carry_out_calculation(option_bag,
option[0],
option[1])
except (PropertiesOptionError, ConfigError) as err:
if isinstance(err, ConfigError):
if not isinstance(err.ori_err, PropertiesOptionError):
raise err
err = err.ori_err
properties = err.proptype
# if not transitive, properties must be verify in current requires
# otherwise if same_action, property must be in properties
@ -586,13 +602,19 @@ class Settings(object):
inverse and value not in expected):
if operator != 'and':
if readable:
if not inverse:
msg = _('the value of "{0}" is {1}')
display_value = display_list(expected, 'or', add_quote=True)
if isinstance(option, tuple):
if not inverse:
msg = _('the calculated value is {0}').format(display_value)
else:
msg = _('the calculated value is not {0}').format(display_value)
else:
msg = _('the value of "{0}" is not {1}')
calc_properties.setdefault(action, []).append(
msg.format(option.impl_get_display_name(),
display_list(expected, 'or', add_quote=True)))
name = option.impl_get_display_name()
if not inverse:
msg = _('the value of "{0}" is {1}').format(name, display_value)
else:
msg = _('the value of "{0}" is not {1}').format(name, display_value)
calc_properties.setdefault(action, []).append(msg)
else:
calc_properties.add(action)
breaked = True

View File

@ -16,10 +16,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
import weakref
from typing import Optional
from typing import Optional, Any, Callable
from .error import ConfigError, PropertiesOptionError
from .setting import owners, undefined, forbidden_owners, OptionBag, ConfigBag
from .autolib import carry_out_calculation
from .function import Params
from .i18n import _
@ -135,26 +136,48 @@ class Values(object):
return self.getdefaultvalue(option_bag)
def getdefaultvalue(self,
option_bag,
force_index: Optional[int]=None):
option_bag):
"""get default value:
- get meta config value or
- get calculated value or
- get default value
:param opt: the `option.Option()` object
:param path: path for `option.Option()` object
:type path: str
:param index: index of a multi/submulti
:type index: int
:returns: default value
"""
moption_bag = self._get_meta(option_bag)
if moption_bag:
# retrieved value from meta config
return moption_bag.config_bag.context.cfgimpl_get_values().get_cached_value(moption_bag)
if option_bag.option.impl_has_callback():
# default value is a calculated value
value = self.calculate_value(option_bag)
if value is not undefined:
return value
# now try to get default value:
value = option_bag.option.impl_getdefault()
# - if option is a submulti, return a list a list
# - if option is a multi, return a list
# - default value
if option_bag.option.impl_is_multi() and option_bag.index is not None:
# if index, must return good value for this index
if len(value) > option_bag.index:
value = value[option_bag.index]
else:
# no value for this index, retrieve default multi value
# default_multi is already a list for submulti
value = option_bag.option.impl_getdefault_multi()
return value
def calculate_value(self,
option_bag: OptionBag) -> Any:
def _reset_cache(_value):
if not 'expire' in option_bag.properties:
return
is_cache, cache_value = self._p_.getcache(option_bag.path,
None,
index,
config_bag.properties,
option_bag.index,
option_bag.config_bag.properties,
option_bag.properties,
'value')
if not is_cache or cache_value == _value:
@ -162,75 +185,55 @@ class Values(object):
# so do not invalidate cache
return
# calculated value is a new value, so reset cache
config_bag.context.cfgimpl_reset_cache(option_bag)
option_bag.config_bag.context.cfgimpl_reset_cache(option_bag)
config_bag = option_bag.config_bag
opt = option_bag.option
if force_index is not None:
index = force_index
else:
index = option_bag.index
moption_bag = self._get_meta(option_bag)
if moption_bag:
# retrieved value from meta config
return moption_bag.config_bag.context.cfgimpl_get_values().get_cached_value(moption_bag)
if opt.impl_has_callback():
# if value has callback, calculate value
callback, callback_params = opt.impl_get_callback()
value = carry_out_calculation(opt,
callback=callback,
callback_params=callback_params,
index=index,
config_bag=config_bag,
fromconsistency=option_bag.fromconsistency)
if isinstance(value, list) and index is not None:
# if value is a list and index is set
if opt.impl_is_submulti() and (value == [] or not isinstance(value[0], list)):
# return value only if it's a submulti and not a list of list
_reset_cache(value)
return value
if len(value) > index:
# return the value for specified index if found
_reset_cache(value[index])
return value[index]
# there is no calculate value for this index,
# so return an other default value
else:
if opt.impl_is_submulti():
if isinstance(value, list):
# value is a list, but no index specified
if (value != [] and not isinstance(value[0], list)):
# if submulti, return a list of value
value = [value]
elif index is not None:
# if submulti, return a list of value
value = [value]
else:
# return a list of list for a submulti
value = [[value]]
elif opt.impl_is_multi() and not isinstance(value, list) and index is None:
# return a list for a multi
value = [value]
# if value has callback, calculate value
callback, callback_params = option_bag.option.impl_get_callback()
value = self.carry_out_calculation(option_bag,
callback,
callback_params)
if isinstance(value, list) and option_bag.index is not None:
# if value is a list and index is set
if option_bag.option.impl_is_submulti() and (value == [] or not isinstance(value[0], list)):
# return value only if it's a submulti and not a list of list
_reset_cache(value)
return value
if len(value) > option_bag.index:
# return the value for specified index if found
_reset_cache(value[option_bag.index])
return value[option_bag.index]
# there is no calculate value for this index,
# so return an other default value
else:
if option_bag.option.impl_is_submulti():
if isinstance(value, list):
# value is a list, but no index specified
if (value != [] and not isinstance(value[0], list)):
# if submulti, return a list of value
value = [value]
elif option_bag.index is not None:
# if submulti, return a list of value
value = [value]
else:
# return a list of list for a submulti
value = [[value]]
elif option_bag.option.impl_is_multi() and not isinstance(value, list) and option_bag.index is None:
# return a list for a multi
value = [value]
_reset_cache(value)
return value
return undefined
# now try to get default value:
# - if opt is a submulti, return a list a list
# - if opt is a multi, return a list
# - default value
value = opt.impl_getdefault()
if opt.impl_is_multi() and index is not None:
# if index, must return good value for this index
if len(value) > index:
value = value[index]
else:
# no value for this index, retrieve default multi value
# default_multi is already a list for submulti
value = opt.impl_getdefault_multi()
return value
def carry_out_calculation(self,
option_bag: OptionBag,
callback: Callable,
callback_params: Optional[Params]) -> Any:
return carry_out_calculation(option_bag.option,
callback=callback,
callback_params=callback_params,
index=option_bag.index,
config_bag=option_bag.config_bag,
fromconsistency=option_bag.fromconsistency)
def isempty(self,
opt,
value,