feat: ParamSelfOption accept dynamic=False

This commit is contained in:
egarette@silique.fr 2025-11-26 09:22:42 +01:00
parent faf694397a
commit 050979f6d3
6 changed files with 167 additions and 78 deletions

View file

@ -39,12 +39,18 @@ def return_true(value, param=None, identifier=None):
raise ValueError('no value') raise ValueError('no value')
def return_no_dyn(value, identifier): def return_no_dyn(value):
if value in [['val', 'val'], ['yes', 'yes']]: if value in [['val', 'val'], ['yes', 'yes']]:
return return
raise ValueError('no value') raise ValueError('no value')
def return_no_dyn_properties(value, identifier):
idx = int(identifier)
if idx and not value[idx - 1]:
return 'disabled'
def return_dynval(value='val', identifier=None): def return_dynval(value='val', identifier=None):
return value return value
@ -1138,7 +1144,7 @@ def test_validator_param_self_option():
out = StrOption('out', '', 'val') out = StrOption('out', '', 'val')
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st_in = StrOption('st_in', '', Calculation(return_dynval, Params(ParamOption(out)))) st_in = StrOption('st_in', '', Calculation(return_dynval, Params(ParamOption(out))))
st = StrOption('st', '', Calculation(return_dynval, Params(ParamOption(st_in))), validators=[Calculation(return_no_dyn, Params((ParamSelfOption(dynamic=False), ParamIdentifier())))]) st = StrOption('st', '', Calculation(return_dynval, Params(ParamOption(st_in))), validators=[Calculation(return_no_dyn, Params((ParamSelfOption(dynamic=False),)))])
dod = DynOptionDescription('dod', '', [st_in, st], identifiers=Calculation(return_list)) dod = DynOptionDescription('dod', '', [st_in, st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod, val1, out]) od = OptionDescription('od', '', [dod, val1, out])
od2 = OptionDescription('od', '', [od]) od2 = OptionDescription('od', '', [od])
@ -1149,6 +1155,28 @@ def test_validator_param_self_option():
cfg.option('od.out').value.set('yes') cfg.option('od.out').value.set('yes')
def test_properties_param_self_option():
out = StrOption('out', '', 'val')
val1 = StrOption('val1', '', ["0", "1", "2"], multi=True)
disabled_property = Calculation(return_no_dyn_properties, Params((ParamSelfOption(dynamic=False), ParamIdentifier())))
st = StrOption('st', '', None, properties=(disabled_property,))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
od = OptionDescription('od', '', [dod, val1, out])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_write()
assert cfg.option('od.dod0.st').value.get() is None
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod1.st').value.get()
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod2.st').value.get()
cfg.option('od.dod0.st').value.set('val')
assert cfg.option('od.dod0.st').value.get() == 'val'
assert cfg.option('od.dod1.st').value.get() is None
with pytest.raises(PropertiesOptionError):
cfg.option('od.dod2.st').value.get()
def test_makedict_dyndescription_context(): def test_makedict_dyndescription_context():
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '') st = StrOption('st', '')

View file

@ -6,7 +6,7 @@ import pytest
from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \ from tiramisu import BoolOption, StrOption, IPOption, NetmaskOption, NetworkOption, BroadcastOption, \
IntOption, OptionDescription, Leadership, Config, Params, ParamValue, ParamOption, \ IntOption, OptionDescription, Leadership, Config, Params, ParamValue, ParamOption, \
ParamSelfOption, ParamIndex, ParamInformation, ParamSelfInformation, ParamSelfOption, Calculation, \ ParamSelfOption, ParamIndex, ParamInformation, ParamSelfInformation, Calculation, \
valid_ip_netmask, valid_network_netmask, \ valid_ip_netmask, valid_network_netmask, \
valid_in_network, valid_broadcast, valid_not_equal valid_in_network, valid_broadcast, valid_not_equal
from tiramisu.setting import groups from tiramisu.setting import groups

View file

@ -172,15 +172,17 @@ class ParamDynOption(ParamOption):
class ParamSelfOption(Param): class ParamSelfOption(Param):
__slots__ = "whole" __slots__ = ("whole", "dynamic")
def __init__( def __init__(
self, self,
whole: bool = undefined, whole: bool = undefined,
dynamic: bool = True,
) -> None: ) -> None:
"""whole: send all value for a multi, not only indexed value""" """whole: send all value for a multi, not only indexed value"""
if whole is not undefined: if whole is not undefined:
self.whole = whole self.whole = whole
self.dynamic = dynamic
class ParamValue(Param): class ParamValue(Param):
@ -613,18 +615,45 @@ def manager_callback(
return subconfig.identifiers[param.identifier_index] return subconfig.identifiers[param.identifier_index]
if isinstance(param, ParamSelfOption): if isinstance(param, ParamSelfOption):
value = calc_self( search_option = subconfig.option
param, if subconfig.option.issubdyn() and not param.dynamic:
index, subconfigs = subconfig.parent.parent.get_common_child(
orig_value, search_option,
config_bag, true_path=subconfig.path,
) validate_properties=False,
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT: )
return value values = []
return { properties = config_bag.context.get_settings().getproperties(
"name": option.impl_get_display_name(subconfig), subconfig,
"value": value, uncalculated=True,
} ) - {'validator'}
for subconfig_ in subconfigs:
if subconfig.path == subconfig_.path:
values.append(orig_value)
else:
subconfig_.properties = properties
values.append(get_value(
config_bag,
subconfig_,
param,
True,
))
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return values
return {"name": search_option.impl_get_display_name(subconfig), "value": values}
else:
value = calc_self(
param,
index,
orig_value,
config_bag,
)
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return value
return {
"name": option.impl_get_display_name(subconfig),
"value": value,
}
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
callbk_option = param.option callbk_option = param.option
@ -762,13 +791,15 @@ def manager_callback(
] ]
values = None values = None
for subconfig in subconfigs: for subconfig in subconfigs:
callbk_option = subconfig.option if isinstance(subconfig, PropertiesOptionError):
value = get_value( value = subconfig
config_bag, else:
subconfig, value = get_value(
param, config_bag,
False, subconfig,
) param,
False,
)
if with_index: if with_index:
value = value[index] value = value[index]
if values is not None: if values is not None:
@ -777,6 +808,8 @@ def manager_callback(
value = values value = values
if callback.__name__ not in FUNCTION_WAITING_FOR_DICT: if callback.__name__ not in FUNCTION_WAITING_FOR_DICT:
return value return value
# FIXME the last one?
callbk_option = subconfig.option
return {"name": callbk_option.impl_get_display_name(subconfig), "value": value} return {"name": callbk_option.impl_get_display_name(subconfig), "value": value}

View file

@ -42,9 +42,15 @@ from . import autolib
def get_common_path(path1, path2): def get_common_path(path1, path2):
if None in (path1, path2):
return None
common_path = commonprefix([path1, path2]) common_path = commonprefix([path1, path2])
if common_path in [path1, path2]: all_paths = [path1, path2]
return common_path if common_path in all_paths:
# od.st is not the common_path of od.st_in
all_paths.remove(common_path)
if all_paths[0].startswith(common_path + '.'):
return common_path
if common_path.endswith("."): if common_path.endswith("."):
return common_path[:-1] return common_path[:-1]
elif "." in common_path: elif "." in common_path:
@ -88,45 +94,52 @@ class CCache:
subconfig, subconfig,
resetted_opts, resetted_opts,
is_default, is_default,
*,
force=False,
): ):
"""reset cache for one option""" """reset cache for one option"""
if subconfig.path in resetted_opts: if not force and subconfig.path in resetted_opts:
return return
resetted_opts.append(subconfig.path) resetted_opts.append(subconfig.path)
config_bag = subconfig.config_bag config_bag = subconfig.config_bag
# if is_default and config_bag.context.get_owner(subconfig) != owners.default: if not force:
# return # if is_default and config_bag.context.get_owner(subconfig) != owners.default:
for is_default, woption in subconfig.option.get_dependencies(subconfig.option): # return
option = woption() for is_default, woption in subconfig.option.get_dependencies(subconfig.option):
if option.issubdyn(): option = woption()
# it's an option in dynoptiondescription, remove cache for all generated option if option.issubdyn():
self.reset_cache_dyn_option( # it's an option in dynoptiondescription, remove cache for all generated option
subconfig, if option.impl_getpath() == subconfig.option.impl_getpath():
option, force = True
resetted_opts, subconfig = subconfig.parent.parent
is_default, self.reset_cache_dyn_option(
) subconfig,
elif option.impl_is_dynoptiondescription(): option,
self.reset_cache_dyn_optiondescription( resetted_opts,
option, is_default,
config_bag, force,
resetted_opts, )
is_default, elif option.impl_is_dynoptiondescription():
) self.reset_cache_dyn_optiondescription(
else: option,
option_subconfig = self.get_sub_config( config_bag,
config_bag, resetted_opts,
option.impl_getpath(), is_default,
None, )
properties=None, else:
validate_properties=False, option_subconfig = self.get_sub_config(
) config_bag,
self.reset_one_option_cache( option.impl_getpath(),
option_subconfig, None,
resetted_opts, properties=None,
is_default, validate_properties=False,
) )
del option self.reset_one_option_cache(
option_subconfig,
resetted_opts,
is_default,
)
del option
subconfig.option.reset_cache( subconfig.option.reset_cache(
subconfig.path, subconfig.path,
config_bag, config_bag,
@ -182,14 +195,18 @@ class CCache:
def get_dynamic_from_dyn_option(self, subconfig, option): def get_dynamic_from_dyn_option(self, subconfig, option):
config_bag = subconfig.config_bag config_bag = subconfig.config_bag
sub_paths = option.impl_getpath() sub_paths = option.impl_getpath()
current_paths = subconfig.path.split(".") if not subconfig.path:
current_paths_max_index = len(current_paths) - 1 current_paths = []
current_paths_max_index = 0
else:
current_paths = subconfig.path.split(".")
current_paths_max_index = len(current_paths) - 1
current_subconfigs = [] current_subconfigs = []
parent = subconfig parent = subconfig
while True: while True:
current_subconfigs.insert(0, parent) current_subconfigs.insert(0, parent)
parent = parent.parent parent = parent.parent
if parent.path is None: if not parent or parent.path is None:
break break
currents = [self.get_root(config_bag)] currents = [self.get_root(config_bag)]
for idx, sub_path in enumerate(sub_paths.split(".")): for idx, sub_path in enumerate(sub_paths.split(".")):
@ -234,12 +251,14 @@ class CCache:
option, option,
resetted_opts, resetted_opts,
is_default, is_default,
force,
): ):
for dyn_option_subconfig in self.get_dynamic_from_dyn_option(subconfig, option): for dyn_option_subconfig in self.get_dynamic_from_dyn_option(subconfig, option):
self.reset_one_option_cache( self.reset_one_option_cache(
dyn_option_subconfig, dyn_option_subconfig,
resetted_opts, resetted_opts,
is_default, is_default,
force=force,
) )
@ -569,10 +588,17 @@ class SubConfig:
parents = [self.parent] parents = [self.parent]
else: else:
if common_path: if common_path:
parent = self.parent
common_parent_number = common_path.count(".") + 1 common_parent_number = common_path.count(".") + 1
for idx in range(current_option_path.count(".") - common_parent_number): parent_count = current_option_path.count(".") - common_parent_number
parent = parent.parent if parent_count >= 0:
parent = self.parent
for idx in range(parent_count):
parent = parent.parent
elif parent_count == 0:
parent = self.parent
else:
# so -1
parent = self
parents = [parent] parents = [parent]
else: else:
common_parent_number = 0 common_parent_number = 0
@ -617,14 +643,16 @@ class SubConfig:
parents = new_parents parents = new_parents
subconfigs = [] subconfigs = []
for parent in parents: for parent in parents:
subconfigs.append( try:
parent.get_child( ret = parent.get_child(
search_option, search_option,
index, index,
validate_properties, validate_properties,
check_dynamic_without_identifiers=check_dynamic_without_identifiers, check_dynamic_without_identifiers=check_dynamic_without_identifiers,
) )
) except PropertiesOptionError as err:
ret = err
subconfigs.append(ret)
if subconfigs_is_a_list: if subconfigs_is_a_list:
return subconfigs return subconfigs
return subconfigs[0] return subconfigs[0]

View file

@ -27,7 +27,7 @@ from itertools import chain
from ..i18n import _ from ..i18n import _
from ..setting import undefined from ..setting import undefined
from ..autolib import Calculation, ParamOption, ParamInformation, ParamSelfInformation from ..autolib import Calculation, ParamOption, ParamSelfOption, ParamInformation, ParamSelfInformation
STATIC_TUPLE = frozenset() STATIC_TUPLE = frozenset()
@ -104,9 +104,7 @@ class Base:
"Calculation" "Calculation"
).format(type(prop), name) ).format(type(prop), name)
) )
for param in chain(prop.params.args, prop.params.kwargs.values()): self.value_dependency(prop, type_="property")
if isinstance(param, ParamOption):
param.option._add_dependency(self, "property")
if properties: if properties:
_setattr(self, "_properties", properties) _setattr(self, "_properties", properties)
self.set_informations(informations) self.set_informations(informations)
@ -395,16 +393,20 @@ class BaseOption(Base):
self, self,
value: Any, value: Any,
is_identifier: bool = False, is_identifier: bool = False,
type_: str = 'default'
) -> Any: ) -> Any:
"""parse dependancy to add dependencies""" """parse dependancy to add dependencies"""
for param in chain(value.params.args, value.params.kwargs.values()): for param in chain(value.params.args, value.params.kwargs.values()):
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
# pylint: disable=protected-access # pylint: disable=protected-access
if is_identifier: if is_identifier:
type_ = "identifier" _type_ = "identifier"
else: else:
type_ = "default" _type_ = type_
param.option._add_dependency(self, type_, is_identifier=is_identifier) param.option._add_dependency(self, _type_, is_identifier=is_identifier)
self._has_dependency = True
elif isinstance(param, ParamSelfOption) and not param.dynamic:
self._add_dependency(self, "self")
self._has_dependency = True self._has_dependency = True
elif isinstance(param, ParamInformation): elif isinstance(param, ParamInformation):
dest = self dest = self

View file

@ -45,9 +45,7 @@ class ChoiceOption(Option):
:param values: is a list of values the option can possibly take :param values: is a list of values the option can possibly take
""" """
if isinstance(values, Calculation): if isinstance(values, Calculation):
for param in chain(values.params.args, values.params.kwargs.values()): self.value_dependency(values, "choice")
if isinstance(param, ParamOption):
param.option._add_dependency(self, "choice")
elif not isinstance(values, tuple): elif not isinstance(values, tuple):
raise TypeError( raise TypeError(
_("values must be a tuple or a calculation for {0}").format(name) _("values must be a tuple or a calculation for {0}").format(name)