From dc1aba2ce9aafdd6ea371f001a962e6fa29f7191 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 19 Feb 2026 20:44:24 +0100 Subject: [PATCH] feat: better separation between mandatory and empty property + get true property type with mandatory function --- tests/test_mandatory.py | 26 ++++++++++++++++++++++---- tests/test_submulti.py | 11 ++++------- tiramisu/api.py | 8 +++++++- tiramisu/option/leadership.py | 3 ++- tiramisu/value.py | 21 ++++++++++++++------- 5 files changed, 49 insertions(+), 20 deletions(-) diff --git a/tests/test_mandatory.py b/tests/test_mandatory.py index 207aa7c..35edfa4 100644 --- a/tests/test_mandatory.py +++ b/tests/test_mandatory.py @@ -208,7 +208,9 @@ def test_mandatory_multi_none(): cfg.option('str3').value.get() except PropertiesOptionError as err: prop = err.proptype - assert 'mandatory' in prop + assert 'empty' in prop + assert cfg.option("str3").value.mandatory() + assert cfg.option("str3").value.mandatory(return_type=True) == "empty" cfg.property.read_write() cfg.option('str3').value.set(['yes', None]) assert cfg.option('str3').owner.get() == 'user' @@ -218,7 +220,7 @@ def test_mandatory_multi_none(): cfg.option('str3').value.get() except PropertiesOptionError as err: prop = err.proptype - assert 'mandatory' in prop + assert 'empty' in prop # assert not list_sessions() @@ -244,7 +246,7 @@ def test_mandatory_multi_empty(): cfg.option('str3').value.get() except PropertiesOptionError as err: prop = err.proptype - assert 'mandatory' in prop + assert 'empty' in prop # cfg.property.read_write() cfg.option('str3').value.set(['yes', '']) @@ -255,7 +257,7 @@ def test_mandatory_multi_empty(): cfg.option('str3').value.get() except PropertiesOptionError as err: prop = err.proptype - assert 'mandatory' in prop + assert 'empty' in prop # assert not list_sessions() @@ -332,10 +334,26 @@ def test_mandatory_warnings_ro(): prop = err.proptype assert 'mandatory' in prop compare(cfg.value.mandatory(), ['str', 'str1', 'unicode2', 'str3']) + assert cfg.option('str').value.mandatory() + assert cfg.option('str1').value.mandatory() + assert cfg.option('unicode2').value.mandatory() + assert cfg.option('str3').value.mandatory() + assert cfg.option('str').value.mandatory(return_type=True) == 'mandatory' + assert cfg.option('str1').value.mandatory(return_type=True) == 'mandatory' + assert cfg.option('unicode2').value.mandatory(return_type=True) == 'mandatory' + assert cfg.option('str3').value.mandatory(return_type=True) == 'mandatory' cfg.property.read_write() cfg.option('str').value.set('a') cfg.property.read_only() compare(cfg.value.mandatory(), ['str1', 'unicode2', 'str3']) + assert not cfg.option('str').value.mandatory() + assert cfg.option('str1').value.mandatory() + assert cfg.option('unicode2').value.mandatory() + assert cfg.option('str3').value.mandatory() + assert cfg.option('str').value.mandatory(return_type=True) is False + assert cfg.option('str1').value.mandatory(return_type=True) == 'mandatory' + assert cfg.option('unicode2').value.mandatory(return_type=True) == 'mandatory' + assert cfg.option('str3').value.mandatory(return_type=True) == 'mandatory' # assert not list_sessions() diff --git a/tests/test_submulti.py b/tests/test_submulti.py index af1bf7d..a8301bc 100644 --- a/tests/test_submulti.py +++ b/tests/test_submulti.py @@ -8,7 +8,7 @@ import warnings from tiramisu.setting import groups, owners from tiramisu import StrOption, IntOption, OptionDescription, submulti, Leadership, Config, \ MetaConfig, Params, ParamOption, Calculation -from tiramisu.error import LeadershipError, PropertiesOptionError +from tiramisu.error import LeadershipError, PropertiesOptionError, ValueOptionError def return_val(val=None): @@ -317,10 +317,8 @@ def test_values_with_leader_and_followers_submulti_mandatory(): cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() # cfg.property.read_write() - cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0", '', "255.255.255.0"]) - cfg.property.read_only() - with pytest.raises(PropertiesOptionError): - cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() + with pytest.raises(ValueOptionError): + cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(["255.255.255.0", "255.255.255.0"]) # assert not list_sessions() @@ -382,7 +380,6 @@ def test_values_with_leader_and_followers_follower_submulti(): cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(['255.255.255.0']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.230.145']) cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(['255.255.255.0']) - cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(['255.255.255.0', '255.255.255.0']) cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.reset() cfg.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set(['255.255.255.0']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.230.145', '192.168.230.145']) @@ -501,7 +498,7 @@ def test_callback_submulti(): def test_callback_submulti_follower(): multi = StrOption('multi', '', multi=True) - multi2 = StrOption('multi2', '', Calculation(return_list), multi=submulti) + multi2 = StrOption('multi2', '', Calculation(return_list), multi=submulti, properties=('notunique',)) od = Leadership('multi', '', [multi, multi2]) od1 = OptionDescription('multi', '', [od]) cfg = Config(od1) diff --git a/tiramisu/api.py b/tiramisu/api.py index c941be8..5f078eb 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -1142,7 +1142,7 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet): """Length for a leadership""" return self._subconfig.parent.get_length_leadership() - def mandatory(self): + def mandatory(self, *, return_type=False): """Return path of options with mandatory property without any value""" subconfig = self._subconfig ori_config_bag = self._subconfig.config_bag @@ -1152,6 +1152,10 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet): self._subconfig.config_bag = config_bag try: if subconfig.option.impl_is_optiondescription(): + if return_type: + raise ConfigError( + _("return_type is not valid for a optiondescription") + ) options = [] for subconfig in config_bag.context.walk( self._subconfig, @@ -1172,6 +1176,8 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet): self._subconfig, only_mandatory=True ) except PropertiesOptionError as err: + if return_type: + return err.proptype[0] return err.proptype == ["mandatory"] or err.proptype == ["empty"] self._subconfig.config_bag = ori_config_bag return False diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index c93f7f3..732a46d 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -73,7 +73,8 @@ class Leadership(OptionDescription): if __debug__: self._check_default_value(child) # remove empty property for follower - child._properties = frozenset(child._properties - {"empty", "unique"}) + if not child.impl_is_submulti(): + child._properties = frozenset(child._properties - {"empty", "unique"}) followers.append(child) child._add_dependency(self, "leadership") child._leadership = weakref.ref(self) diff --git a/tiramisu/value.py b/tiramisu/value.py index c26e678..ebd7249 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -268,8 +268,8 @@ class Values: force_allow_empty_list: bool, ) -> bool: """convenience method to know if an option is empty""" - index = subconfig.index option = subconfig.option + index = subconfig.index if index is None and option.impl_is_submulti(): # index is not set isempty = True @@ -277,9 +277,16 @@ class Values: isempty = self._isempty_multi(val, force_allow_empty_list) if isempty: break - elif ( - index is None or (index is not None and option.impl_is_submulti()) - ) and option.impl_is_multi(): + elif option.impl_is_submulti(): + if index is None: + isempty = False + for v in value: + if self._isempty_multi(v, force_allow_empty_list): + isempty = True + break + else: + isempty = self._isempty_multi(value, force_allow_empty_list) + elif index is None and option.impl_is_multi(): # it's a single list isempty = self._isempty_multi(value, force_allow_empty_list) else: @@ -293,9 +300,9 @@ class Values: ) -> bool: if not isinstance(value, list): return False - return ( - (not force_allow_empty_list and value == []) or None in value or "" in value - ) + if not force_allow_empty_list: + return value == [] + return None in value or "" in value # ______________________________________________________________________ # set value