From cab8dae15a9317bcd82fce9f4f98a4f46338ebe3 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Wed, 13 Mar 2019 08:49:18 +0100 Subject: [PATCH] requirement can have callback --- test/test_option_callback.py | 5 - test/test_requires.py | 494 ++++++++++++++++++++++++++- tiramisu/api.py | 5 +- tiramisu/autolib.py | 4 +- tiramisu/error.py | 8 +- tiramisu/function.py | 16 +- tiramisu/option/baseoption.py | 95 +++--- tiramisu/option/leadership.py | 2 +- tiramisu/option/optiondescription.py | 2 +- tiramisu/setting.py | 102 +++--- tiramisu/value.py | 155 ++++----- 11 files changed, 712 insertions(+), 176 deletions(-) diff --git a/test/test_option_callback.py b/test/test_option_callback.py index 53b896f..967f10c 100644 --- a/test/test_option_callback.py +++ b/test/test_option_callback.py @@ -1240,7 +1240,6 @@ def test_calc_value_condition(): def test_calc_value_allow_none(): - from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue val1 = StrOption('val1', "", 'val1') val2 = StrOption('val2', "") val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True))) @@ -1250,7 +1249,6 @@ def test_calc_value_allow_none(): def test_calc_value_remove_duplicate(): - from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue val1 = StrOption('val1', "", 'val1') val2 = StrOption('val2', "", 'val1') val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True))) @@ -1260,7 +1258,6 @@ def test_calc_value_remove_duplicate(): def test_calc_value_join(): - from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue val1 = StrOption('val1', "", 'val1') val2 = StrOption('val2', "", 'val2') val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.'))) @@ -1270,7 +1267,6 @@ def test_calc_value_join(): def test_calc_value_min(): - from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue val1 = StrOption('val1', "", 'val1') val2 = StrOption('val2', "", 'val2') val3 = StrOption('val3', "", 'val3') @@ -1284,7 +1280,6 @@ def test_calc_value_min(): def test_calc_value_add(): - from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue val1 = IntOption('val1', "", 1) val2 = IntOption('val2', "", 2) val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add'))) diff --git a/test/test_requires.py b/test/test_requires.py index d5124d4..45f9350 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -8,7 +8,7 @@ from tiramisu.setting import groups from tiramisu import setting setting.expires_time = 1 from tiramisu import IPOption, OptionDescription, BoolOption, IntOption, StrOption, \ - Leadership, Config + Leadership, Config, calc_value, Params, ParamOption from tiramisu.error import PropertiesOptionError, RequirementError from py.test import raises from tiramisu.storage import list_sessions, delete_session @@ -65,6 +65,27 @@ def test_requires(): api.option('ip_address_service').value.get() +def test_requires_callback(): + a = BoolOption('activate_service', '', True) + b = IPOption('ip_address_service', '', + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(a)), 'expected': False, 'action': 'disabled'}]) + od = OptionDescription('service', '', [a, b]) + api = Config(od) + api.property.read_write() + assert not api.option('activate_service').option.requires() + assert api.option('ip_address_service').option.requires() + api.option('ip_address_service').value.get() + api.option('activate_service').value.set(False) + props = [] + try: + api.option('ip_address_service').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + api.option('activate_service').value.set(True) + api.option('ip_address_service').value.get() + + def test_requires_inverse(): a = BoolOption('activate_service', '', True) b = IPOption('ip_address_service', '', @@ -179,6 +200,44 @@ def test_requires_same_action(): assert frozenset(props) == frozenset(['disabled']) +def test_requires_same_action_callback(): + activate_service = BoolOption('activate_service', '', True) + activate_service_web = BoolOption('activate_service_web', '', True, + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(activate_service)), 'expected': False, + 'action': 'new'}]) + + ip_address_service_web = IPOption('ip_address_service_web', '', + requires=[{'option': activate_service_web, 'expected': False, + 'action': 'disabled', 'inverse': False, + 'transitive': True, 'same_action': False}]) + od1 = OptionDescription('service', '', [activate_service, activate_service_web, ip_address_service_web]) + api = Config(od1) + api.property.read_write() + api.property.add('new') + api.option('activate_service').value.get() + api.option('activate_service_web').value.get() + api.option('ip_address_service_web').value.get() + api.option('activate_service').value.set(False) + # + props = [] + try: + api.option('activate_service_web').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['new']) + # + props = [] + try: + api.option('ip_address_service_web').value.get() + except PropertiesOptionError as err: + props = err.proptype + submsg = '"disabled" (' + _('the calculated value is {0}').format('"False"') + ')' + assert str(err) == str(_('cannot access to {0} "{1}" because has {2} {3}').format('option', 'ip_address_service_web', 'property', submsg)) + #access to cache + assert str(err) == str(_('cannot access to {0} "{1}" because has {2} {3}').format('option', 'ip_address_service_web', 'property', submsg)) + assert frozenset(props) == frozenset(['disabled']) + + def test_multiple_requires(): a = StrOption('activate_service', '') b = IPOption('ip_address_service', '', @@ -326,6 +385,36 @@ def test_requires_transitive(): assert frozenset(props) == frozenset(['disabled']) +def test_requires_transitive_callback(): + a = BoolOption('activate_service', '', True) + b = BoolOption('activate_service_web', '', True, + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(a)), 'expected': False, 'action': 'disabled'}]) + + d = IPOption('ip_address_service_web', '', + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(b)), 'expected': False, 'action': 'disabled'}]) + od = OptionDescription('service', '', [a, b, d]) + api = Config(od) + api.property.read_write() + api.option('activate_service').value.get() + api.option('activate_service_web').value.get() + api.option('ip_address_service_web').value.get() + api.option('activate_service').value.set(False) + # + props = [] + try: + api.option('activate_service_web').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + # + props = [] + try: + api.option('ip_address_service_web').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + + def test_requires_transitive_unrestraint(): a = BoolOption('activate_service', '', True) b = BoolOption('activate_service_web', '', True, @@ -589,6 +678,46 @@ def test_requires_multi_disabled(): assert frozenset(props) == frozenset(['disabled']) +def test_requires_multi_disabled_callback(): + a = BoolOption('activate_service', '') + b = IntOption('num_service', '') + c = IPOption('ip_address_service', '', + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(a)), 'expected': True, 'action': 'disabled'}, + {'callback': calc_value, 'callback_params': Params(ParamOption(b)), 'expected': 1, 'action': 'disabled'}]) + od = OptionDescription('service', '', [a, b, c]) + api = Config(od) + api.property.read_write() + + api.option('ip_address_service').value.get() + + api.option('activate_service').value.set(True) + props = [] + try: + api.option('ip_address_service').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + + api.option('activate_service').value.set(False) + api.option('ip_address_service').value.get() + + api.option('num_service').value.set(1) + props = [] + try: + api.option('ip_address_service').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + + api.option('activate_service').value.set(True) + props = [] + try: + api.option('ip_address_service').value.get() + except PropertiesOptionError as err: + props = err.proptype + assert frozenset(props) == frozenset(['disabled']) + + def test_requires_multi_disabled_new_format(): a = BoolOption('activate_service', '') b = IntOption('num_service', '') @@ -1006,6 +1135,56 @@ def test_leadership_requires(): del ret['ip_admin_eth0.netmask_admin_eth0'] +def test_leadership_requires_callback(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(ip_admin_eth0)), 'expected': '192.168.1.1', 'action': 'disabled'}]) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + maconfig = OptionDescription('toto', '', [interface1]) + api = Config(maconfig) + api.property.read_write() + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == [] + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.2'] + # + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2', '192.168.1.1']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get()") + # + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2', '192.168.1.2']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get() is None + api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.set('255.255.255.255') + assert api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get() == '255.255.255.255' + assert api.value.dict() == {'ip_admin_eth0.ip_admin_eth0': ['192.168.1.2', '192.168.1.2'], + 'ip_admin_eth0.netmask_admin_eth0': [None, '255.255.255.255']} + # + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2', '192.168.1.1']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get()") + ret = api.value.dict() + assert set(ret.keys()) == set(['ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert ret['ip_admin_eth0.ip_admin_eth0'] == ['192.168.1.2', '192.168.1.1'] + assert len(ret['ip_admin_eth0.netmask_admin_eth0']) == 2 + assert ret['ip_admin_eth0.netmask_admin_eth0'][0] is None + assert isinstance(ret['ip_admin_eth0.netmask_admin_eth0'][1], PropertiesOptionError) + del ret['ip_admin_eth0.netmask_admin_eth0'][1] + del ret['ip_admin_eth0.netmask_admin_eth0'][0] + del ret['ip_admin_eth0.netmask_admin_eth0'] + # + api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.set('255.255.255.255') + ret = api.value.dict() + assert set(ret.keys()) == set(['ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) + assert ret['ip_admin_eth0.ip_admin_eth0'] == ['192.168.1.2', '192.168.1.1'] + assert len(ret['ip_admin_eth0.netmask_admin_eth0']) == 2 + assert ret['ip_admin_eth0.netmask_admin_eth0'][0] == '255.255.255.255' + assert isinstance(ret['ip_admin_eth0.netmask_admin_eth0'][1], PropertiesOptionError) + del ret['ip_admin_eth0.netmask_admin_eth0'][1] + del ret['ip_admin_eth0.netmask_admin_eth0'][0] + del ret['ip_admin_eth0.netmask_admin_eth0'] + + def test_leadership_requires_both(): ip_admin = StrOption('ip_admin_eth0', "ip réseau autorisé") ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True, @@ -1037,7 +1216,7 @@ def test_leadership_requires_properties(): netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, requires=[{'option': ip_admin_eth0, 'expected': '192.168.1.1', 'action': 'disabled'}]) Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0], properties=('hidden',), - requires=[{'option': ip_admin, 'expected': '192.168.1.1', 'action': 'disabled'}]) + requires=[{'option': ip_admin, 'expected': '192.168.1.1', 'action': 'disabled'}]) def test_leadership_requires_leader(): @@ -1068,6 +1247,34 @@ def test_leadership_requires_leader(): assert api.value.dict() == {'activate': False} +def test_leadership_requires_leader_callback(): + activate = BoolOption('activate', "Activer l'accès au réseau", True) + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True, + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(activate)), 'expected': False, 'action': 'disabled'}]) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + maconfig = OptionDescription('toto', '', [activate, interface1]) + api = Config(maconfig) + api.property.read_write() + # + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == [] + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.2'] + # + api.option('activate').value.set(False) + raises(PropertiesOptionError, "api.option('ip_admin_eth0.ip_admin_eth0').value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + # + api.option('activate').value.set(True) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + # + api.option('activate').value.set(False) + raises(PropertiesOptionError, "api.option('ip_admin_eth0.ip_admin_eth0').value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + assert api.value.dict() == {'activate': False} + + def test_leadership_requires_leadership(): activate = BoolOption('activate', "Activer l'accès au réseau", True) ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) @@ -1096,6 +1303,34 @@ def test_leadership_requires_leadership(): assert api.value.dict() == {'activate': False} +def test_leadership_requires_leadership_callback(): + activate = BoolOption('activate', "Activer l'accès au réseau", True) + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0], + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(activate)), 'expected': False, 'action': 'disabled'}]) + maconfig = OptionDescription('toto', '', [activate, interface1]) + api = Config(maconfig) + api.property.read_write() + # + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == [] + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.2'] + # + api.option('activate').value.set(False) + raises(PropertiesOptionError, "api.option('ip_admin_eth0.ip_admin_eth0').value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + # + api.option('activate').value.set(True) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + # + api.option('activate').value.set(False) + raises(PropertiesOptionError, "api.option('ip_admin_eth0.ip_admin_eth0').value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + assert api.value.dict() == {'activate': False} + + def test_leadership_requires_no_leader(): activate = BoolOption('activate', "Activer l'accès au réseau", True) ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) @@ -1125,6 +1360,40 @@ def test_leadership_requires_no_leader(): assert api.value.dict() == {'ip_admin_eth0.ip_admin_eth0': ['192.168.1.2', '192.168.1.1'], 'activate': False} +def test_leadership_requires_no_leader_callback(): + activate = BoolOption('activate', "Activer l'accès au réseau", True) + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, + requires=[{'callback': calc_value, 'callback_params': Params(ParamOption(activate)), 'expected': False, 'action': 'disabled'}]) + interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + maconfig = OptionDescription('toto', '', [activate, interface1]) + api = Config(maconfig) + api.property.read_write() + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == [] + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2']) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.2'] + api.option('activate').value.set(False) + api.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2', '192.168.1.1']) + assert api.option('ip_admin_eth0.ip_admin_eth0').value.get() == ['192.168.1.2', '192.168.1.1'] + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get()") + api.option('activate').value.set(True) + assert api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get() is None + assert api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get() is None + api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.set('255.255.255.255') + assert api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get() == '255.255.255.255' + api.option('activate').value.set(False) + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 0).value.get()") + raises(PropertiesOptionError, "api.option('ip_admin_eth0.netmask_admin_eth0', 1).value.get()") + dico = api.value.dict() + assert set(dico.keys()) == {'activate', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0'} + dico['ip_admin_eth0.ip_admin_eth0'] == ['192.168.1.2', '192.168.1.1'] + dico['activate'] == False + del dico['ip_admin_eth0.netmask_admin_eth0'][1] + del dico['ip_admin_eth0.netmask_admin_eth0'][0] + + def test_leadership_requires_complet(): optiontoto = StrOption('unicodetoto', "Unicode leader") option = StrOption('unicode', "Unicode leader", multi=True) @@ -1212,6 +1481,106 @@ def test_leadership_requires_complet(): del dico['options.unicode.unicode7'] +def test_leadership_requires_complet_callback(): + optiontoto = StrOption('unicodetoto', "Unicode leader") + option = StrOption('unicode', "Unicode leader", multi=True) + option1 = StrOption('unicode1', "Unicode follower 1", multi=True) + option2 = StrOption('unicode2', "Values 'test' must show 'Unicode follower 3'", multi=True) + option3 = StrOption('unicode3', "Unicode follower 3", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(option)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}], + multi=True) + option4 = StrOption('unicode4', "Unicode follower 4", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(option2)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}], + multi=True) + option5 = StrOption('unicode5', "Unicode follower 5", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(optiontoto)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}], + multi=True) + option6 = StrOption('unicode6', "Unicode follower 6", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(optiontoto)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}, + {'callback': calc_value, + 'callback_params': Params(ParamOption(option2)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}], + multi=True) + option7 = StrOption('unicode7', "Unicode follower 7", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(option2)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}, + {'callback': calc_value, + 'callback_params': Params(ParamOption(optiontoto)), + 'expected': u'test', + 'action': 'hidden', + 'inverse': True}], + multi=True) + descr1 = Leadership("unicode", "Common configuration 1", + [option, option1, option2, option3, option4, option5, option6, option7]) + descr = OptionDescription("options", "Common configuration 2", [descr1, optiontoto]) + descr = OptionDescription("unicode1_leadership_requires", "Leader followers with Unicode follower 3 hidden when Unicode follower 2 is test", [descr]) + config = Config(descr) + config.property.read_write() + config.option('options.unicode.unicode').value.set(['test', 'trah']) + config.option('options.unicode.unicode2', 0).value.set('test') + dico = config.value.dict() + assert dico.keys() == set(['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicode.unicode5', 'options.unicode.unicode6', 'options.unicode.unicode7', 'options.unicodetoto']) + assert dico['options.unicode.unicode'] == ['test', 'trah'] + assert dico['options.unicode.unicode1'] == [None, None] + assert dico['options.unicode.unicode2'] == ['test', None] + assert dico['options.unicode.unicode3'][0] is None + assert isinstance(dico['options.unicode.unicode3'][1], PropertiesOptionError) + assert dico['options.unicode.unicode4'][0] is None + assert isinstance(dico['options.unicode.unicode4'][1], PropertiesOptionError) + assert dico['options.unicodetoto'] is None + del dico['options.unicode.unicode3'][1] + del dico['options.unicode.unicode3'] + del dico['options.unicode.unicode4'][1] + del dico['options.unicode.unicode4'] + del dico['options.unicode.unicode5'][0] + del dico['options.unicode.unicode5'][0] + del dico['options.unicode.unicode6'][0] + del dico['options.unicode.unicode6'][0] + del dico['options.unicode.unicode7'][0] + del dico['options.unicode.unicode7'][0] + # + config.option('options.unicodetoto').value.set('test') + dico = config.value.dict() + assert dico.keys() == set(['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicode.unicode5', 'options.unicode.unicode6', 'options.unicode.unicode7', 'options.unicodetoto']) + assert dico['options.unicode.unicode'] == ['test', 'trah'] + assert dico['options.unicode.unicode1'] == [None, None] + assert dico['options.unicode.unicode2'] == ['test', None] + assert dico['options.unicode.unicode3'][0] is None + assert isinstance(dico['options.unicode.unicode3'][1], PropertiesOptionError) + assert dico['options.unicode.unicode4'][0] is None + assert isinstance(dico['options.unicode.unicode4'][1], PropertiesOptionError) + assert dico['options.unicode.unicode5'] == [None, None] + assert dico['options.unicode.unicode6'][0] is None + assert isinstance(dico['options.unicode.unicode6'][1], PropertiesOptionError) + assert dico['options.unicode.unicode7'][0] is None + assert isinstance(dico['options.unicode.unicode7'][1], PropertiesOptionError) + assert dico['options.unicodetoto'] == 'test' + del dico['options.unicode.unicode3'][1] + del dico['options.unicode.unicode3'] + del dico['options.unicode.unicode4'][1] + del dico['options.unicode.unicode4'] + del dico['options.unicode.unicode6'][1] + del dico['options.unicode.unicode6'] + del dico['options.unicode.unicode7'][1] + del dico['options.unicode.unicode7'] + + def test_leadership_requires_transitive(): optiontoto = StrOption('unicodetoto', "Unicode leader") option = StrOption('unicode', "Unicode leader", multi=True) @@ -1325,3 +1694,124 @@ def test_leadership_requires_transitive(): del (dico['options.unicode.unicode4'][2]) del (dico['options.unicode.unicode4'][1]) del (dico['options.unicode.unicode4'][0]) + + +def test_leadership_requires_transitive_callback(): + optiontoto = StrOption('unicodetoto', "Unicode leader") + option = StrOption('unicode', "Unicode leader", multi=True) + option1 = StrOption('unicode1', "Unicode follower 1", multi=True) + option2 = StrOption('unicode2', "Unicode follower 2", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(optiontoto)), + 'expected': u'test', + 'action': 'disabled', + 'transitive': True, + 'inverse': True}], + multi=True) + option3 = StrOption('unicode3', "Unicode follower 3", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(option2)), + 'expected': u'test', + 'action': 'disabled', + 'transitive': True, + 'inverse': True}], + multi=True) + option4 = StrOption('unicode4', "Unicode follower 4", requires=[{'callback': calc_value, + 'callback_params': Params(ParamOption(option3)), + 'expected': u'test', + 'action': 'disabled', + 'transitive': True, + 'inverse': True}], + multi=True) + descr1 = Leadership("unicode", "Common configuration 1", + [option, option1, option2, option3, option4]) + descr = OptionDescription("options", "Common configuration 2", [descr1, optiontoto]) + descr = OptionDescription("unicode1", "", [descr]) + config = Config(descr) + config.property.read_write() + assert config.value.dict() == {'options.unicode.unicode': [], 'options.unicode.unicode1': [], 'options.unicode.unicode2': [], 'options.unicode.unicode3': [], 'options.unicode.unicode4': [], 'options.unicodetoto': None} + # + config.option('options.unicodetoto').value.set('test') + assert config.value.dict() == {'options.unicode.unicode': [], 'options.unicode.unicode1': [], 'options.unicode.unicode2': [], 'options.unicode.unicode3': [], 'options.unicode.unicode4': [], 'options.unicodetoto': 'test'} + # + config.option('options.unicode.unicode').value.set(['a', 'b', 'c']) + dico = config.value.dict() + assert list(dico.keys()) == ['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicodetoto'] + assert dico['options.unicodetoto'] == 'test' + assert dico['options.unicode.unicode'] == ['a', 'b', 'c'] + assert dico['options.unicode.unicode1'] == [None, None, None] + assert dico['options.unicode.unicode2'] == [None, None, None] + assert isinstance(dico['options.unicode.unicode3'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][2], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][2], PropertiesOptionError) + del (dico['options.unicode.unicode3'][2]) + del (dico['options.unicode.unicode3'][1]) + del (dico['options.unicode.unicode3'][0]) + del (dico['options.unicode.unicode4'][2]) + del (dico['options.unicode.unicode4'][1]) + del (dico['options.unicode.unicode4'][0]) + # + config.option('options.unicode.unicode2', 1).value.set('test') + config.option('options.unicode.unicode3', 1).value.set('test') + dico = config.value.dict() + assert list(dico.keys()) == ['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicodetoto'] + assert dico['options.unicodetoto'] == 'test' + assert dico['options.unicode.unicode'] == ['a', 'b', 'c'] + assert dico['options.unicode.unicode1'] == [None, None, None] + assert dico['options.unicode.unicode2'] == [None, 'test', None] + assert isinstance(dico['options.unicode.unicode3'][0], PropertiesOptionError) + assert dico['options.unicode.unicode3'][1] == 'test' + assert isinstance(dico['options.unicode.unicode3'][2], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][0], PropertiesOptionError) + assert dico['options.unicode.unicode4'][1] == None + assert isinstance(dico['options.unicode.unicode4'][2], PropertiesOptionError) + del (dico['options.unicode.unicode3'][2]) + del (dico['options.unicode.unicode3'][1]) + del (dico['options.unicode.unicode3'][0]) + del (dico['options.unicode.unicode4'][2]) + del (dico['options.unicode.unicode4'][1]) + del (dico['options.unicode.unicode4'][0]) + # + config.option('options.unicode.unicode2', 1).value.set('rah') + dico = config.value.dict() + assert list(dico.keys()) == ['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicodetoto'] + assert dico['options.unicodetoto'] == 'test' + assert dico['options.unicode.unicode'] == ['a', 'b', 'c'] + assert dico['options.unicode.unicode1'] == [None, None, None] + assert dico['options.unicode.unicode2'] == [None, 'rah', None] + assert isinstance(dico['options.unicode.unicode3'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][2], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][2], PropertiesOptionError) + del (dico['options.unicode.unicode3'][2]) + del (dico['options.unicode.unicode3'][1]) + del (dico['options.unicode.unicode3'][0]) + del (dico['options.unicode.unicode4'][2]) + del (dico['options.unicode.unicode4'][1]) + del (dico['options.unicode.unicode4'][0]) + # + config.option('options.unicode.unicode2', 1).value.set('test') + config.option('options.unicodetoto').value.set('rah') + dico = config.value.dict() + assert list(dico.keys()) == ['options.unicode.unicode', 'options.unicode.unicode1', 'options.unicode.unicode2', 'options.unicode.unicode3', 'options.unicode.unicode4', 'options.unicodetoto'] + assert dico['options.unicodetoto'] == 'rah' + assert dico['options.unicode.unicode'] == ['a', 'b', 'c'] + assert dico['options.unicode.unicode1'] == [None, None, None] + assert isinstance(dico['options.unicode.unicode3'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode3'][2], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][0], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][1], PropertiesOptionError) + assert isinstance(dico['options.unicode.unicode4'][2], PropertiesOptionError) + del (dico['options.unicode.unicode2'][2]) + del (dico['options.unicode.unicode2'][1]) + del (dico['options.unicode.unicode2'][0]) + del (dico['options.unicode.unicode3'][2]) + del (dico['options.unicode.unicode3'][1]) + del (dico['options.unicode.unicode3'][0]) + del (dico['options.unicode.unicode4'][2]) + del (dico['options.unicode.unicode4'][1]) + del (dico['options.unicode.unicode4'][0]) diff --git a/tiramisu/api.py b/tiramisu/api.py index bc0e1ab..b91f39a 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -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, diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index dc2af0c..ffdf87d 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -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, diff --git a/tiramisu/error.py b/tiramisu/error.py index 940eee2..1e27319 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -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): diff --git a/tiramisu/function.py b/tiramisu/function.py index 94837a1..1be9e6e 100644 --- a/tiramisu/function.py +++ b/tiramisu/function.py @@ -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: diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index bbcef07..e44a7f4 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -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: diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index d7c0a3a..86e0efb 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -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(), diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index b858fb3..e51d9d0 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -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: diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 8ce365d..6e674ea 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -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 diff --git a/tiramisu/value.py b/tiramisu/value.py index 6b07455..08f974d 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -16,10 +16,11 @@ # along with this program. If not, see . # ____________________________________________________________ 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,