feat: property.get(uncalculated=True) is now possible for an dynoptiondescription without identifiers

This commit is contained in:
egarette@silique.fr 2025-10-15 09:11:33 +02:00
parent e8921795d3
commit e0f16b14c7
5 changed files with 69 additions and 14 deletions

View file

@ -13,7 +13,7 @@ from tiramisu import BoolOption, StrOption, ChoiceOption, IPOption, \
Config, \
Params, ParamOption, ParamValue, ParamIdentifier, ParamSelfOption, ParamDynOption, ParamIndex, ParamSelfInformation, ParamInformation, \
Calculation, calc_value
from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError, ValueOptionError
from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError, ValueOptionError, AttributeOptionError
def display_name(kls, subconfig, with_quote=False) -> str:
@ -317,6 +317,20 @@ def test_prop_dyndescription():
# assert not list_sessions()
def test_prop_dyndescription_uncalculated():
st = StrOption('st', '', properties=('test',))
od = OptionDescription('od', '', [st], properties=('test_od',))
dod = DynOptionDescription('dod', '', [od], identifiers=Calculation(return_list))
od2 = OptionDescription('od', '', [dod])
cfg = Config(od2)
assert set(cfg.option('dod.od').property.get(uncalculated=True)) == {'test_od'}
assert set(cfg.option('dod.od.st').property.get(uncalculated=True)) == {'test', 'validator'}
with pytest.raises(AttributeOptionError):
set(cfg.option('dod.od').property.get())
with pytest.raises(AttributeOptionError):
set(cfg.option('dod.od.st').property.get())
def test_prop_dyndescription_force_store_value():
st = StrOption('st', '', properties=('force_store_value',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
@ -451,6 +465,30 @@ def test_callback_dyndescription_outside_optional():
# assert not list_sessions()
def test_dyndescription_subdyn():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', 'val1')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
out = StrOption('out', '', Calculation(return_dynval, Params(ParamDynOption(st, ['val1', None]))), multi=True, properties=('notunique',))
dod2 = DynOptionDescription('dod2', '', [dod, out], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod2])
od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2)
cfg.property.read_write()
assert cfg.option('od.dod2val1.dodval1.st').value.get() == 'val1'
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dodval1.st').value.get()
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2val1.dod.st').value.get()
assert set(cfg.option('od.dod2val1.dodval1.st').property.get()) == {'validator'}
assert set(cfg.option('od.dod2val1.dodval1.st').property.get(uncalculated=True)) == {'validator'}
assert set(cfg.option('od.dod2.dod.st').property.get(uncalculated=True)) == {'validator'}
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dodval1.st').property.get(uncalculated=True)
with pytest.raises(AttributeOptionError):
cfg.option('od.dod2val1.dod.st').property.get(uncalculated=True)
def test_callback_dyndescription_subdyn():
lst = StrOption('lst', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', 'val1')

View file

@ -26,6 +26,7 @@ from .error import (
LeadershipError,
ValueErrorWarning,
PropertiesOptionError,
AttributeOptionError,
)
from .i18n import _
from .setting import (
@ -123,7 +124,6 @@ class TiramisuHelp:
class CommonTiramisu(TiramisuHelp):
_validate_properties = True
_allow_dynoption = False
def _set_subconfig(self) -> None:
if not self._subconfig:
@ -133,7 +133,7 @@ class CommonTiramisu(TiramisuHelp):
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
allow_dynoption=True,
)
except AssertionError as err:
raise ConfigError(str(err))
@ -149,8 +149,6 @@ def option_type(typ):
@wraps(func)
def wrapped(*args, **kwargs):
self = args[0]
if isinstance(typ, list) and "allow_dynoption" in typ:
self._allow_dynoption = True
config_bag = self._config_bag
if self._config_bag.context.impl_type == "group" and "group" in types:
options_bag = [
@ -164,6 +162,9 @@ def option_type(typ):
kwargs["is_group"] = True
return func(self, options_bag, *args[1:], **kwargs)
self._set_subconfig()
if (not isinstance(typ, list) or "allow_dynoption" not in typ) and self._subconfig.is_dynamic_without_identifiers:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
option = self._subconfig.option
error_type = None
if "dynamic" in types:
@ -668,7 +669,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
_validate_properties = False
@option_type(["option", "optiondescription", "with_index", "symlink"])
@option_type(["option", "optiondescription", "with_index", "symlink", "allow_dynoption"])
def get(
self,
*,
@ -677,6 +678,8 @@ class TiramisuOptionProperty(CommonTiramisuOption):
uncalculated: bool = False,
):
"""Get properties for an option"""
if self._subconfig.is_dynamic_without_identifiers and not uncalculated:
raise AttributeOptionError(self._subconfig.path, "option-dynamic")
settings = self._config_bag.context.get_settings()
if not only_raises:
return settings.getproperties(
@ -797,9 +800,8 @@ class TiramisuOptionInformation(CommonTiramisuOption):
"""Manage option's informations"""
_validate_properties = False
_allow_dynoption = True
@option_type(["option", "optiondescription", "with_or_without_index", "symlink"])
@option_type(["option", "optiondescription", "with_or_without_index", "symlink", "allow_dynoption"])
def get(
self,
name: str,
@ -812,7 +814,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
default,
)
@option_type(["option", "optiondescription"])
@option_type(["option", "optiondescription", "allow_dynoption"])
def set(self, key: str, value: Any) -> None:
"""Set information"""
self._config_bag.context.get_values().set_information(
@ -821,7 +823,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
value,
)
@option_type(["option", "optiondescription"])
@option_type(["option", "optiondescription", "allow_dynoption"])
def remove(
self,
key: str,
@ -832,7 +834,7 @@ class TiramisuOptionInformation(CommonTiramisuOption):
path=self._path,
)
@option_type(["option", "optiondescription", "with_or_without_index", "symlink"])
@option_type(["option", "optiondescription", "with_or_without_index", "symlink", "allow_dynoption"])
def list(self) -> list:
"""List information's keys"""
lst1 = set(self._subconfig.option._list_information())
@ -1106,7 +1108,6 @@ class TiramisuOption(
self._path = path
self._index = index
self._config_bag = config_bag
self._allow_dynoption = allow_dynoption
self._subconfig = subconfig
if not self._registers:
_registers(self._registers, "TiramisuOption")

View file

@ -25,7 +25,7 @@ from copy import copy, deepcopy
from typing import Optional, List, Any, Union
from os.path import commonprefix
from .error import PropertiesOptionError, ConfigError, ConflictError, LeadershipError
from .error import PropertiesOptionError, ConfigError, ConflictError, LeadershipError, AttributeOptionError
from .option import DynOptionDescription, Leadership, Option
from .setting import ConfigBag, Settings, undefined, groups
from .value import Values, owners
@ -237,6 +237,7 @@ class SubConfig:
"apply_requires",
"transitive_properties",
"is_dynamic",
"is_dynamic_without_identifiers",
"identifiers",
"_length",
)
@ -254,6 +255,7 @@ class SubConfig:
# for python 3.9 properties: Union[list[str], undefined] = undefined,
properties=undefined,
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
) -> None:
self.index = index
self.identifiers = identifiers
@ -269,10 +271,17 @@ class SubConfig:
)
self.apply_requires = not is_follower or index is not None
self.true_path = true_path
if parent and parent.is_dynamic or self.option.impl_is_dynoptiondescription():
if self.option.impl_is_dynoptiondescription():
self.is_dynamic = True
self.is_dynamic_without_identifiers = identifiers is None or (parent and identifiers == parent.identifiers)
if check_dynamic_without_identifiers and parent and parent.is_dynamic and self.is_dynamic_without_identifiers != parent.is_dynamic_without_identifiers:
raise AttributeOptionError(true_path, "option-dynamic")
elif parent:
self.is_dynamic = parent.is_dynamic
self.is_dynamic_without_identifiers = parent.is_dynamic_without_identifiers
else:
self.is_dynamic = False
self.is_dynamic_without_identifiers = False
self._properties = properties
if validate_properties:
if self.path and self._properties is undefined:
@ -433,6 +442,7 @@ class SubConfig:
check_index: bool = True,
config_bag: ConfigBag = None,
true_path: Optional[str] = None,
check_dynamic_without_identifiers: bool = True,
) -> "SubConfig":
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if config_bag is None:
@ -461,6 +471,7 @@ class SubConfig:
properties=properties,
validate_properties=validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
)
if check_index and index is not None:
if option.impl_is_optiondescription() or not option.impl_is_follower():
@ -512,6 +523,7 @@ class SubConfig:
search_option: "BaseOption",
true_path: Optional[str] = None,
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
):
current_option_path = self.option.impl_getpath()
search_option_path = search_option.impl_getpath()
@ -571,6 +583,7 @@ class SubConfig:
None,
validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
)
)
parents = new_parents
@ -581,6 +594,7 @@ class SubConfig:
search_option,
index,
validate_properties,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
)
)
if subconfigs_is_a_list:

View file

@ -115,6 +115,7 @@ class DynOptionDescription(OptionDescription):
None,
False,
properties=None,
check_dynamic_without_identifiers=False,
)
identifiers = self._identifiers
if isinstance(identifiers, list):

View file

@ -395,6 +395,7 @@ class Values:
woption(),
true_path=subconfig.path,
validate_properties=False,
check_dynamic_without_identifiers=False,
)
if not isinstance(options, list):
options = [options]