feat: reactive metaconfig

This commit is contained in:
egarette@silique.fr 2025-09-11 22:07:53 +02:00
parent ac35f2428b
commit 47c413e60c
26 changed files with 3020 additions and 2932 deletions

View file

@ -847,7 +847,7 @@ msgstr ""
#: tiramisu/option/option.py:294 #: tiramisu/option/option.py:294
msgid "the value \"{}\" is not unique" msgid "the value \"{}\" is not unique"
msgstr "la valeur de \"{}\" n'est pas unique" msgstr "la valeur \"{}\" n'est pas unique"
#: tiramisu/option/option.py:356 #: tiramisu/option/option.py:356
msgid "which must not be a list" msgid "which must not be a list"

View file

@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2025-05-12 09:05+0200\n" "POT-Creation-Date: 2025-06-28 10:15+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,95 +15,95 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"
#: tiramisu/api.py:79 #: tiramisu/api.py:86
msgid "Settings:" msgid "Settings:"
msgstr "" msgstr ""
#: tiramisu/api.py:83 #: tiramisu/api.py:90
msgid "Access to option without verifying permissive properties" msgid "Access to option without verifying permissive properties"
msgstr "" msgstr ""
#: tiramisu/api.py:88 #: tiramisu/api.py:95
msgid "Access to option without property restriction" msgid "Access to option without property restriction"
msgstr "" msgstr ""
#: tiramisu/api.py:93 #: tiramisu/api.py:100
msgid "Do not warnings during validation" msgid "Do not warnings during validation"
msgstr "" msgstr ""
#: tiramisu/api.py:97 #: tiramisu/api.py:104
msgid "Commands:" msgid "Commands:"
msgstr "" msgstr ""
#: tiramisu/api.py:111 tiramisu/api.py:1857 #: tiramisu/api.py:118 tiramisu/api.py:1920
msgid "please specify a valid sub function ({0}.{1})" msgid "please specify a valid sub function ({0}.{1})"
msgstr "" msgstr ""
#: tiramisu/api.py:196 #: tiramisu/api.py:206
msgid "please do not specify index ({0}.{1})" msgid "please do not specify index ({0}.{1})"
msgstr "" msgstr ""
#: tiramisu/api.py:201 tiramisu/api.py:856 #: tiramisu/api.py:211 tiramisu/api.py:906
msgid "please specify index with a follower option ({0}.{1})" msgid "please specify index with a follower option ({0}.{1})"
msgstr "" msgstr ""
#: tiramisu/api.py:222 #: tiramisu/api.py:232
msgid "please specify a valid sub function ({0}.{1}): {2}" msgid "please specify a valid sub function ({0}.{1}): {2}"
msgstr "" msgstr ""
#: tiramisu/api.py:446 #: tiramisu/api.py:493
msgid "the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True" msgid "the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True"
msgstr "" msgstr ""
#: tiramisu/api.py:532 #: tiramisu/api.py:581
msgid "cannot get option from a follower symlink without index" msgid "cannot get option from a follower symlink without index"
msgstr "" msgstr ""
#: tiramisu/api.py:607 #: tiramisu/api.py:657
msgid "cannot add this property: \"{0}\"" msgid "cannot add this property: \"{0}\""
msgstr "" msgstr ""
#: tiramisu/api.py:634 #: tiramisu/api.py:684
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\"" msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\""
msgstr "" msgstr ""
#: tiramisu/api.py:638 #: tiramisu/api.py:688
msgid "cannot find \"{0}\" in option \"{1}\"" msgid "cannot find \"{0}\" in option \"{1}\""
msgstr "" msgstr ""
#: tiramisu/api.py:643 #: tiramisu/api.py:693
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\" at index \"{2}\"" msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\" at index \"{2}\""
msgstr "" msgstr ""
#: tiramisu/api.py:647 #: tiramisu/api.py:697
msgid "cannot find \"{0}\" in option \"{1}\" at index \"{2}\"" msgid "cannot find \"{0}\" in option \"{1}\" at index \"{2}\""
msgstr "" msgstr ""
#: tiramisu/api.py:691 #: tiramisu/api.py:741
msgid "cannot find \"{0}\"" msgid "cannot find \"{0}\""
msgstr "" msgstr ""
#: tiramisu/api.py:873 #: tiramisu/api.py:923
msgid "only multi value has defaultmulti" msgid "only multi value has defaultmulti"
msgstr "" msgstr ""
#: tiramisu/api.py:1037 #: tiramisu/api.py:1087
msgid "please specify a valid sub function ({0}.{1}) for {2}" msgid "please specify a valid sub function ({0}.{1}) for {2}"
msgstr "" msgstr ""
#: tiramisu/api.py:1424 #: tiramisu/api.py:1485
msgid "properties must be a frozenset" msgid "properties must be a frozenset"
msgstr "" msgstr ""
#: tiramisu/api.py:1428 tiramisu/api.py:1455 #: tiramisu/api.py:1489 tiramisu/api.py:1516
msgid "unknown when {} (must be in append or remove)" msgid "unknown when {} (must be in append or remove)"
msgstr "" msgstr ""
#: tiramisu/api.py:1441 tiramisu/api.py:1465 tiramisu/config.py:1676 #: tiramisu/api.py:1502 tiramisu/api.py:1526 tiramisu/config.py:1722
msgid "unknown type {}" msgid "unknown type {}"
msgstr "" msgstr ""
#: tiramisu/api.py:1829 #: tiramisu/api.py:1892
msgid "do not use unrestraint, nowarnings or forcepermissive together" msgid "do not use unrestraint, nowarnings or forcepermissive together"
msgstr "" msgstr ""
@ -207,79 +207,83 @@ msgstr ""
msgid "unexpected error \"{1}\" in function \"{2}\" for option {0}" msgid "unexpected error \"{1}\" in function \"{2}\" for option {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:574 #: tiramisu/config.py:609
msgid "there is no option description for this config (may be GroupConfig)" msgid "there is no option description for this config (may be GroupConfig)"
msgstr "" msgstr ""
#: tiramisu/config.py:663 #: tiramisu/config.py:698
msgid "no option found in config with these criteria" msgid "no option found in config with these criteria"
msgstr "" msgstr ""
#: tiramisu/config.py:978 tiramisu/option/optiondescription.py:74 #: tiramisu/config.py:1024 tiramisu/option/optiondescription.py:74
msgid "option description seems to be part of an other config" msgid "option description seems to be part of an other config"
msgstr "" msgstr ""
#: tiramisu/config.py:1140 #: tiramisu/config.py:1186
msgid "parent of {0} not already exists" msgid "parent of {0} not already exists"
msgstr "" msgstr ""
#: tiramisu/config.py:1187 #: tiramisu/config.py:1233
msgid "cannot set leadership object has root optiondescription" msgid "cannot set leadership object has root optiondescription"
msgstr "" msgstr ""
#: tiramisu/config.py:1190 #: tiramisu/config.py:1236
msgid "cannot set dynoptiondescription object has root optiondescription" msgid "cannot set dynoptiondescription object has root optiondescription"
msgstr "" msgstr ""
#: tiramisu/config.py:1242 #: tiramisu/config.py:1282
msgid "child must be a Config, GroupConfig, MixConfig or MetaConfig"
msgstr ""
#: tiramisu/config.py:1290
msgid "config name must be uniq in groupconfig for \"{0}\"" msgid "config name must be uniq in groupconfig for \"{0}\""
msgstr "" msgstr ""
#: tiramisu/config.py:1453 #: tiramisu/config.py:1499
msgid "unknown config \"{}\"" msgid "unknown config \"{}\""
msgstr "" msgstr ""
#: tiramisu/config.py:1478 #: tiramisu/config.py:1524
msgid "child must be a Config, MixConfig or MetaConfig" msgid "child must be a Config, MixConfig or MetaConfig"
msgstr "" msgstr ""
#: tiramisu/config.py:1513 #: tiramisu/config.py:1559
msgid "force_default, force_default_if_same or force_dont_change_value cannot be set with only_config" msgid "force_default, force_default_if_same or force_dont_change_value cannot be set with only_config"
msgstr "" msgstr ""
#: tiramisu/config.py:1523 #: tiramisu/config.py:1569
msgid "force_default and force_dont_change_value cannot be set together" msgid "force_default and force_dont_change_value cannot be set together"
msgstr "" msgstr ""
#: tiramisu/config.py:1672 #: tiramisu/config.py:1718
msgid "config name must be uniq in groupconfig for {0}" msgid "config name must be uniq in groupconfig for {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:1717 #: tiramisu/config.py:1763
msgid "config added has no name, the name is mandatory" msgid "config added has no name, the name is mandatory"
msgstr "" msgstr ""
#: tiramisu/config.py:1722 #: tiramisu/config.py:1768
msgid "config name \"{0}\" is not uniq in groupconfig \"{1}\"" msgid "config name \"{0}\" is not uniq in groupconfig \"{1}\""
msgstr "" msgstr ""
#: tiramisu/config.py:1740 tiramisu/config.py:1746 #: tiramisu/config.py:1786 tiramisu/config.py:1792
msgid "cannot find the config {0}" msgid "cannot find the config {0}"
msgstr "" msgstr ""
#: tiramisu/config.py:1772 #: tiramisu/config.py:1818
msgid "MetaConfig with optiondescription must have string has child, not {}" msgid "MetaConfig with optiondescription must have string has child, not {}"
msgstr "" msgstr ""
#: tiramisu/config.py:1784 #: tiramisu/config.py:1830
msgid "child must be a Config or MetaConfig" msgid "child must be a Config or MetaConfig"
msgstr "" msgstr ""
#: tiramisu/config.py:1789 #: tiramisu/config.py:1835
msgid "all config in metaconfig must have the same optiondescription" msgid "all config in metaconfig must have the same optiondescription"
msgstr "" msgstr ""
#: tiramisu/config.py:1806 #: tiramisu/config.py:1852
msgid "metaconfig must have the same optiondescription" msgid "metaconfig must have the same optiondescription"
msgstr "" msgstr ""
@ -391,23 +395,23 @@ msgstr ""
msgid "the \"{0}\" function must not return a list (\"{1}\") for the follower option {2}" msgid "the \"{0}\" function must not return a list (\"{1}\") for the follower option {2}"
msgstr "" msgstr ""
#: tiramisu/error.py:331 #: tiramisu/error.py:333
msgid "invalid value" msgid "invalid value"
msgstr "" msgstr ""
#: tiramisu/error.py:341 #: tiramisu/error.py:343
msgid "attention, \"{0}\" could be an invalid {1} for {2}" msgid "attention, \"{0}\" could be an invalid {1} for {2}"
msgstr "" msgstr ""
#: tiramisu/error.py:345 #: tiramisu/error.py:347
msgid "attention, \"{0}\" could be an invalid {1} for {2} at index \"{3}\"" msgid "attention, \"{0}\" could be an invalid {1} for {2} at index \"{3}\""
msgstr "" msgstr ""
#: tiramisu/error.py:366 tiramisu/error.py:377 #: tiramisu/error.py:368 tiramisu/error.py:379
msgid "\"{0}\" is an invalid {1} for {2}" msgid "\"{0}\" is an invalid {1} for {2}"
msgstr "" msgstr ""
#: tiramisu/error.py:368 #: tiramisu/error.py:370
msgid "\"{0}\" is an invalid {1} for {2} at index \"{3}\"" msgid "\"{0}\" is an invalid {1} for {2} at index \"{3}\""
msgstr "" msgstr ""
@ -499,19 +503,19 @@ msgstr ""
msgid "invalid string" msgid "invalid string"
msgstr "" msgstr ""
#: tiramisu/option/choiceoption.py:47 #: tiramisu/option/choiceoption.py:52
msgid "values must be a tuple or a calculation for {0}" msgid "values must be a tuple or a calculation for {0}"
msgstr "" msgstr ""
#: tiramisu/option/choiceoption.py:70 #: tiramisu/option/choiceoption.py:75
msgid "the calculated values \"{0}\" for \"{1}\" is not a list" msgid "the calculated values \"{0}\" for \"{1}\" is not a list"
msgstr "" msgstr ""
#: tiramisu/option/choiceoption.py:101 #: tiramisu/option/choiceoption.py:106
msgid "only \"{0}\" is allowed" msgid "only \"{0}\" is allowed"
msgstr "" msgstr ""
#: tiramisu/option/choiceoption.py:103 #: tiramisu/option/choiceoption.py:108
msgid "only {0} are allowed" msgid "only {0} are allowed"
msgstr "" msgstr ""
@ -728,19 +732,19 @@ msgstr ""
msgid "invalid default_multi value \"{0}\" for option {1}, must be a list for a submulti" msgid "invalid default_multi value \"{0}\" for option {1}, must be a list for a submulti"
msgstr "" msgstr ""
#: tiramisu/option/option.py:294 #: tiramisu/option/option.py:291
msgid "the value \"{}\" is not unique" msgid "the value \"{}\" is not unique"
msgstr "" msgstr ""
#: tiramisu/option/option.py:356 #: tiramisu/option/option.py:353
msgid "which must not be a list" msgid "which must not be a list"
msgstr "" msgstr ""
#: tiramisu/option/option.py:408 tiramisu/option/option.py:434 #: tiramisu/option/option.py:408 tiramisu/option/option.py:446
msgid "which must be a list" msgid "which must be a list"
msgstr "" msgstr ""
#: tiramisu/option/option.py:428 #: tiramisu/option/option.py:436
msgid "which \"{}\" must be a list of list" msgid "which \"{}\" must be a list of list"
msgstr "" msgstr ""
@ -805,27 +809,27 @@ msgstr ""
msgid "too weak" msgid "too weak"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:80 #: tiramisu/option/portoption.py:77
msgid "inconsistency in allowed range" msgid "inconsistency in allowed range"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:85 #: tiramisu/option/portoption.py:82
msgid "max value is empty" msgid "max value is empty"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:98 #: tiramisu/option/portoption.py:95
msgid "range must have two values only" msgid "range must have two values only"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:101 #: tiramisu/option/portoption.py:98
msgid "first port in range must be smaller than the second one" msgid "first port in range must be smaller than the second one"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:127 #: tiramisu/option/portoption.py:124
msgid "should be between {0} and {1}" msgid "should be between {0} and {1}"
msgstr "" msgstr ""
#: tiramisu/option/portoption.py:129 #: tiramisu/option/portoption.py:126
msgid "must be between {0} and {1}" msgid "must be between {0} and {1}"
msgstr "" msgstr ""
@ -905,19 +909,15 @@ msgstr ""
msgid "unknown action {}" msgid "unknown action {}"
msgstr "" msgstr ""
#: tiramisu/value.py:570 tiramisu/value.py:872 #: tiramisu/value.py:557 tiramisu/value.py:859
msgid "set owner \"{0}\" is forbidden" msgid "set owner \"{0}\" is forbidden"
msgstr "" msgstr ""
#: tiramisu/value.py:577 #: tiramisu/value.py:564
msgid "\"{0}\" is a default value, so we cannot change owner to \"{1}\"" msgid "\"{0}\" is a default value, so we cannot change owner to \"{1}\""
msgstr "" msgstr ""
#: tiramisu/value.py:751 #: tiramisu/value.py:845
msgid "index {index} is greater than the length {length} for option {subconfig.option.impl_get_display_name(with_quote=True)}"
msgstr ""
#: tiramisu/value.py:858
msgid "information's item not found \"{}\"" msgid "information's item not found \"{}\""
msgstr "" msgstr ""

View file

@ -1,5 +1,4 @@
# from json import dumps, loads # from json import dumps, loads
import asyncio
from os import environ from os import environ
try: try:
from tiramisu_api import Config from tiramisu_api import Config
@ -63,3 +62,14 @@ def parse_od_get(dico):
else: else:
ret[k.path()] = v ret[k.path()] = v
return ret return ret
def get_dependencies(option):
ret = []
for a in option.dependencies():
if a.isoptiondescription():
ret.append((a.path(), None))
else:
ret.append((a.path(), a.index()))
ret.sort()
return ret

View file

@ -191,7 +191,6 @@ def test_choiceoption_calc_opt_function_propertyerror():
# assert not list_sessions() # assert not list_sessions()
#def test_choiceoption_calc_opt_multi_function(config_type):
def test_choiceoption_calc_opt_multi_function(): def test_choiceoption_calc_opt_multi_function():
# FIXME # FIXME
config_type = 'tiramisu' config_type = 'tiramisu'

View file

@ -13,12 +13,6 @@ from tiramisu.error import PropertiesOptionError, ValueWarning, ConfigError
import warnings import warnings
#def teardown_function(function):
# # test_od_not_list emit a warnings because of doesn't create a Config
# with warnings.catch_warnings(record=True) as w:
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def make_description(): def make_description():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False) gcdummy = BoolOption('dummy', 'dummy', default=False)

View file

@ -80,15 +80,15 @@ def test_copy_force_store_value():
assert conf.value.exportation() == {'creole.general.wantref': {None: [True, 'user']}} assert conf.value.exportation() == {'creole.general.wantref': {None: [True, 'user']}}
assert conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}} assert conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert not list_sessions() # assert not list_sessions()
#
#
#def test_copy_force_store_value_metaconfig(): def test_copy_force_store_value_metaconfig():
# od1 = make_description() od1 = make_description()
# meta = MetaConfig([], optiondescription=od1) meta = MetaConfig([], optiondescription=od1)
# conf = meta.config.new() conf = meta.config.new()
# assert meta.property.get() == conf.property.get() assert meta.property.get() == conf.property.get()
# assert meta.permissive.get() == conf.permissive.get() assert meta.permissive.get() == conf.permissive.get()
# conf.property.read_write() conf.property.read_write()
# assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}} assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert meta.value.exportation() == {} assert meta.value.exportation() == {}
## assert not list_sessions() # assert not list_sessions()

View file

@ -1,7 +1,7 @@
# coding: utf-8 # coding: utf-8
from .autopath import do_autopath from .autopath import do_autopath
do_autopath() do_autopath()
from .config import parse_od_get from .config import parse_od_get, get_dependencies
import pytest import pytest
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
@ -336,6 +336,7 @@ def test_prop_dyndescription_force_store_value_calculation_prefix():
od2 = OptionDescription('od', '', [od]) od2 = OptionDescription('od', '', [od])
cfg = Config(od2) cfg = Config(od2)
cfg.property.read_write() cfg.property.read_write()
assert get_dependencies(cfg.option('od.lst')) == [('od.dodval1', None), ('od.dodval2', None)]
assert cfg.option('od.dodval1.st').owner.isdefault() == False assert cfg.option('od.dodval1.st').owner.isdefault() == False
assert cfg.option('od.dodval2.st').owner.isdefault() == False assert cfg.option('od.dodval2.st').owner.isdefault() == False
assert parse_od_get(cfg.value.get()) == {'od.lst': ['val1', 'val2'], 'od.dodval1.st': 'val1', 'od.dodval2.st': 'val2'} assert parse_od_get(cfg.value.get()) == {'od.lst': ['val1', 'val2'], 'od.dodval1.st': 'val1', 'od.dodval2.st': 'val2'}
@ -394,6 +395,8 @@ def test_callback_dyndescription_outside1():
od = OptionDescription('od', '', [dod, out]) od = OptionDescription('od', '', [dod, out])
od2 = OptionDescription('od', '', [od, lst]) od2 = OptionDescription('od', '', [od, lst])
cfg = Config(od2) cfg = Config(od2)
#
assert get_dependencies(cfg.option('od.dodval1.st')) == [('od.out', None)]
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val', 'od.dodval2.st': 'val', 'od.out': 'val', 'lst': ['val1', 'val2']} assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val', 'od.dodval2.st': 'val', 'od.out': 'val', 'lst': ['val1', 'val2']}
cfg.option('od.dodval1.st').value.set('val1') cfg.option('od.dodval1.st').value.set('val1')
cfg.option('od.dodval2.st').value.set('val2') cfg.option('od.dodval2.st').value.set('val2')
@ -417,6 +420,8 @@ def test_callback_dyndescription_outside2():
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': None, 'od.dodval2.st': None, 'od.out': None, 'lst': ['val1', 'val2']} assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': None, 'od.dodval2.st': None, 'od.out': None, 'lst': ['val1', 'val2']}
cfg.option('od.out').value.set('val1') cfg.option('od.out').value.set('val1')
assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val1', 'od.dodval2.st': 'val1', 'od.out': 'val1', 'lst': ['val1', 'val2']} assert parse_od_get(cfg.value.get()) == {'od.dodval1.st': 'val1', 'od.dodval2.st': 'val1', 'od.out': 'val1', 'lst': ['val1', 'val2']}
#
assert get_dependencies(cfg.option('od.out')) == [('od.dodval1.st', None), ('od.dodval2.st', None)]
# assert not list_sessions() # assert not list_sessions()
@ -1191,6 +1196,8 @@ def test_leadership_dyndescription():
cfg = Config(od1) cfg = Config(od1)
owner = cfg.owner.get() owner = cfg.owner.get()
# #
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert parse_od_get(cfg.value.get()) == {'od.stval2.st1.st1': [], 'od.stval1.st1.st1': []} assert parse_od_get(cfg.value.get()) == {'od.stval2.st1.st1': [], 'od.stval1.st1.st1': []}
assert cfg.option('od.stval1.st1.st1').value.get() == [] assert cfg.option('od.stval1.st1.st1').value.get() == []
assert cfg.option('od.stval2.st1.st1').value.get() == [] assert cfg.option('od.stval2.st1.st1').value.get() == []
@ -1250,6 +1257,9 @@ def test_leadership_dyndescription_force_store_value_leader():
od1 = OptionDescription('od', '', [od]) od1 = OptionDescription('od', '', [od])
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
#
assert get_dependencies(cfg.option('od.stval1.st1.st2')) == [('od.stval1.st1', None)]
assert get_dependencies(cfg.option('od.stval2.st1.st2')) == [('od.stval2.st1', None)]
assert cfg.option('od.stval1.st1.st1').owner.isdefault() == False assert cfg.option('od.stval1.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval2.st1.st1').owner.isdefault() == False assert cfg.option('od.stval2.st1.st1').owner.isdefault() == False
assert cfg.option('od.stval1.st1.st2', 0).owner.isdefault() == True assert cfg.option('od.stval1.st1.st2', 0).owner.isdefault() == True

View file

@ -1055,7 +1055,7 @@ def test_follower_force_store_value_reset():
# assert not list_sessions() # assert not list_sessions()
#def test_follower_properties(): def test_follower_properties():
ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=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, properties=('aproperty',)) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True, properties=('aproperty',))
interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) interface1 = Leadership('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])

View file

@ -13,8 +13,6 @@ from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import groups from tiramisu.setting import groups
#def teardown_function(function):
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def is_mandatory(variable): def is_mandatory(variable):
return True return True
@ -688,25 +686,25 @@ def return_list(val=None, identifier=None):
return ['val1', 'val2'] return ['val1', 'val2']
#def test_mandatory_dyndescription(): def test_mandatory_dyndescription():
# st = StrOption('st', '', properties=('mandatory',)) st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list)) dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
# od = OptionDescription('od', '', [dod]) od = OptionDescription('od', '', [dod])
# od2 = OptionDescription('od', '', [od]) od2 = OptionDescription('od', '', [od])
# cfg = Config(od2) cfg = Config(od2)
# cfg.property.read_only() cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st']) compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
#
#
#def test_mandatory_dyndescription_context(): def test_mandatory_dyndescription_context():
# val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
# st = StrOption('st', '', properties=('mandatory',)) st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1)))) dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
# od = OptionDescription('od', '', [dod, val1]) od = OptionDescription('od', '', [dod, val1])
# od2 = OptionDescription('od', '', [od]) od2 = OptionDescription('od', '', [od])
# cfg = Config(od2) cfg = Config(od2)
# cfg.property.read_only() cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st']) compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_callback_leader_and_followers_leader(): def test_mandatory_callback_leader_and_followers_leader():

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -15,76 +15,76 @@ def make_metaconfig():
return MetaConfig([], optiondescription=od2, name='metacfg1') return MetaConfig([], optiondescription=od2, name='metacfg1')
#def test_multi_parents_path(): def test_multi_parents_path():
# """ """
# metacfg1 (1) --- metacfg1 (1) ---
# | -- cfg1 | -- cfg1
# metacfg2 (2) --- metacfg2 (2) ---
# """ """
# metacfg1 = make_metaconfig() metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1") cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2') metacfg2 = MetaConfig([cfg1], name='metacfg2')
# # #
# assert metacfg1.config.path() == 'metacfg1' assert metacfg1.config.path() == 'metacfg1'
# assert metacfg2.config.path() == 'metacfg2' assert metacfg2.config.path() == 'metacfg2'
# assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1' assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
#
#
#def test_multi_parents_path_same(): def test_multi_parents_path_same():
# """ """
# --- metacfg2 (1) --- --- metacfg2 (1) ---
# metacfg1 --| | -- cfg1 metacfg1 --| | -- cfg1
# --- metacfg3 (2) --- --- metacfg3 (2) ---
# """ """
# metacfg1 = make_metaconfig() metacfg1 = make_metaconfig()
# metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2") metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
# metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3") metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
# cfg1 = metacfg2.config.new(type='config', name="cfg1") cfg1 = metacfg2.config.new(type='config', name="cfg1")
# metacfg3.config.add(cfg1) metacfg3.config.add(cfg1)
# # #
# assert metacfg2.config.path() == 'metacfg1.metacfg2' assert metacfg2.config.path() == 'metacfg1.metacfg2'
# assert metacfg3.config.path() == 'metacfg1.metacfg3' assert metacfg3.config.path() == 'metacfg1.metacfg3'
# assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1' assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
# metacfg1.option('od1.i1').value.set(1) metacfg1.option('od1.i1').value.set(1)
# metacfg3.option('od1.i1').value.set(2) metacfg3.option('od1.i1').value.set(2)
# assert cfg1.option('od1.i1').value.get() == 1 assert cfg1.option('od1.i1').value.get() == 1
# orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1') orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
# deep = orideep deep = orideep
# while True: while True:
# try: try:
# children = list(deep.config.list()) children = list(deep.config.list())
# except: except:
# break break
# assert len(children) < 2 assert len(children) < 2
# deep = children[0] deep = children[0]
# assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1' assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
# assert cfg1.option('od1.i1').value.get() == 1 assert cfg1.option('od1.i1').value.get() == 1
#
#
#
#def test_multi_parents_value(): def test_multi_parents_value():
# metacfg1 = make_metaconfig() metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1") cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2') metacfg2 = MetaConfig([cfg1], name='metacfg2')
# # #
# assert cfg1.option('od1.i1').value.get() == None assert cfg1.option('od1.i1').value.get() == None
# assert cfg1.option('od1.i2').value.get() == 1 assert cfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i3').value.get() == None assert cfg1.option('od1.i3').value.get() == None
# # #
# assert metacfg1.option('od1.i1').value.get() == None assert metacfg1.option('od1.i1').value.get() == None
# assert metacfg1.option('od1.i2').value.get() == 1 assert metacfg1.option('od1.i2').value.get() == 1
# assert metacfg1.option('od1.i3').value.get() == None assert metacfg1.option('od1.i3').value.get() == None
# # #
# assert metacfg2.option('od1.i1').value.get() == None assert metacfg2.option('od1.i1').value.get() == None
# assert metacfg2.option('od1.i2').value.get() == 1 assert metacfg2.option('od1.i2').value.get() == 1
# assert metacfg2.option('od1.i3').value.get() == None assert metacfg2.option('od1.i3').value.get() == None
# # #
# metacfg1.option('od1.i3').value.set(3) metacfg1.option('od1.i3').value.set(3)
# assert metacfg1.option('od1.i3').value.get() == 3 assert metacfg1.option('od1.i3').value.get() == 3
# assert cfg1.option('od1.i3').value.get() == 3 assert cfg1.option('od1.i3').value.get() == 3
# assert metacfg2.option('od1.i2').value.get() == 1 assert metacfg2.option('od1.i2').value.get() == 1
# # #
# metacfg2.option('od1.i2').value.set(4) metacfg2.option('od1.i2').value.set(4)
# assert metacfg2.option('od1.i2').value.get() == 4 assert metacfg2.option('od1.i2').value.get() == 4
# assert metacfg1.option('od1.i2').value.get() == 1 assert metacfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i2').value.get() == 4 assert cfg1.option('od1.i2').value.get() == 4

View file

@ -13,7 +13,7 @@ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation valid_ip_netmask, ParamSelfOption, ParamInformation, ParamSelfInformation
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _ from tiramisu.i18n import _
from .config import config_type, get_config, parse_od_get from .config import config_type, get_config, parse_od_get, get_dependencies
def return_val(): def return_val():
@ -693,6 +693,7 @@ def test_callback_leader_and_followers_leader(config_type):
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
assert [(a.path(), a.index()) for a in cfg.option('val1').dependencies()] == [('val2.val2', None)]
assert cfg.option('val1').value.get() == ['val'] assert cfg.option('val1').value.get() == ['val']
assert cfg.option('val2.val2').value.get() == ['val'] assert cfg.option('val2.val2').value.get() == ['val']
# #
@ -783,10 +784,29 @@ def test_callback_leader_and_followers_leader2(config_type):
cfg = Config(od1) cfg = Config(od1)
cfg.property.read_write() cfg.property.read_write()
cfg = get_config(cfg, config_type) cfg = get_config(cfg, config_type)
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None)] #, ('val2.val2', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)] #, ('val2.val3', 0)]
#
cfg.option('val1.val1').value.set(['val']) cfg.option('val1.val1').value.set(['val'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
assert cfg.option('val1.val4', 0).value.get() == 'val2' assert cfg.option('val1.val4', 0).value.get() == 'val2'
assert cfg.option('val1.val3', 0).value.get() == 'val2' assert cfg.option('val1.val3', 0).value.get() == 'val2'
assert cfg.option('val1.val2', 0).value.get() == 'val2' assert cfg.option('val1.val2', 0).value.get() == 'val2'
#
cfg.option('val1.val1').value.set(['val1', 'val2'])
assert get_dependencies(cfg.option('val1.val2')) == [('val1', None), ('val1.val3', 0), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val2', 0)) == [('val1', None), ('val1.val3', 0)]
assert get_dependencies(cfg.option('val1.val2', 1)) == [('val1', None), ('val1.val3', 1)]
assert get_dependencies(cfg.option('val1.val3')) == [('val1', None), ('val1.val4', 0), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val3', 0)) == [('val1', None), ('val1.val4', 0)]
assert get_dependencies(cfg.option('val1.val3', 1)) == [('val1', None), ('val1.val4', 1)]
assert get_dependencies(cfg.option('val1.val4')) == [('val1', None)]
# assert not list_sessions() # assert not list_sessions()
@ -1499,6 +1519,7 @@ def test_leadership_callback_description(config_type):
cfg.option('od.st.st1.st2', 0).value.set('yes') cfg.option('od.st.st1.st2', 0).value.set('yes')
assert cfg.option('od.st.st1.st1').owner.get() == owner assert cfg.option('od.st.st1.st1').owner.get() == owner
assert cfg.option('od.st.st1.st2', 0).owner.get() == owner assert cfg.option('od.st.st1.st2', 0).owner.get() == owner
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None)]
# assert not list_sessions() # assert not list_sessions()
@ -1515,6 +1536,8 @@ def test_leadership_callback_outside(config_type):
owner = cfg.owner.get() owner = cfg.owner.get()
cfg.option('od.st.st1.st1').value.set(['yes']) cfg.option('od.st.st1.st1').value.set(['yes'])
assert parse_od_get(cfg.value.get()) == {'od.st.st1.st1': [{'od.st.st1.st1': 'yes', 'od.st.st1.st2': 'val2'}], 'od.st.st3': ['val2']} assert parse_od_get(cfg.value.get()) == {'od.st.st1.st1': [{'od.st.st1.st1': 'yes', 'od.st.st1.st2': 'val2'}], 'od.st.st3': ['val2']}
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None), ('od.st.st3', None,)]
## assert not list_sessions() ## assert not list_sessions()

View file

@ -154,8 +154,8 @@ def test_force_default_on_freeze_multi():
# with pytest.raises(ConfigError): # with pytest.raises(ConfigError):
# Config(od1) # Config(od1)
# assert not list_sessions() # assert not list_sessions()
#
#
#def test_force_metaconfig_on_freeze_leader(): #def test_force_metaconfig_on_freeze_leader():
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',)) # dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
# dummy2 = BoolOption('dummy2', 'Test string option', multi=True) # dummy2 = BoolOption('dummy2', 'Test string option', multi=True)

View file

@ -818,27 +818,27 @@ def test_reset_properties_force_store_value():
# assert not list_sessions() # assert not list_sessions()
#def test_importation_force_store_value(): def test_importation_force_store_value():
# gcdummy = BoolOption('dummy', 'dummy', default=False, gcdummy = BoolOption('dummy', 'dummy', default=False,
# properties=('force_store_value',)) properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy]) gcgroup = OptionDescription('gc', '', [gcdummy])
# od1 = OptionDescription('tiramisu', '', [gcgroup]) od1 = OptionDescription('tiramisu', '', [gcgroup])
# config1 = Config(od1) config1 = Config(od1)
# assert config1.value.exportation() == {} assert config1.value.exportation() == {}
# config1.property.add('frozen') config1.property.add('frozen')
# assert config1.value.exportation() == {} assert config1.value.exportation() == {}
# config1.property.add('force_store_value') config1.property.add('force_store_value')
# assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}} assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# exportation = config1.property.exportation() exportation = config1.property.exportation()
# config2 = Config(od1) config2 = Config(od1)
# assert config2.value.exportation() == {} assert config2.value.exportation() == {}
# config2.property.importation(exportation) config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}} assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# config2.property.importation(exportation) config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}} assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
## assert not list_sessions() # assert not list_sessions()
#
#
def test_set_modified_value(): def test_set_modified_value():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',)) gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy]) gcgroup = OptionDescription('gc', '', [gcdummy])
@ -920,18 +920,18 @@ def test_set_modified_value():
# assert not list_sessions() # assert not list_sessions()
#def test_none_is_not_modified(): def test_none_is_not_modified():
# gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',)) gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
# gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',)) gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1]) gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
# od1 = OptionDescription('tiramisu', '', [gcgroup]) od1 = OptionDescription('tiramisu', '', [gcgroup])
# cfg = Config(od1) cfg = Config(od1)
# assert cfg.value.exportation() == {} assert cfg.value.exportation() == {}
# cfg.property.read_write() cfg.property.read_write()
# assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}} assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
## assert not list_sessions() # assert not list_sessions()
#
#
def test_pprint(): def test_pprint():
msg_error = _("cannot access to {0} {1} because has {2} {3}") msg_error = _("cannot access to {0} {1} because has {2} {3}")
msg_is_not = _('the value of "{0}" is not {1}') msg_is_not = _('the value of "{0}" is not {1}')

View file

@ -34,7 +34,7 @@ def return_val(value, param=None):
def return_if_val(value): def return_if_val(value):
if value != 'val': if value not in ['val', 'val_not_raise']:
raise ValueError('test error') raise ValueError('test error')
@ -341,6 +341,13 @@ def test_validator_multi(config_type):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val', 'val1']) cfg.option('opt1').value.set(['val', 'val1'])
assert len(w) == 1 assert len(w) == 1
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val1', 'val2'])
assert len(w) == 2
with warnings.catch_warnings(record=True) as w:
# same value twice
cfg.option('opt1').value.set(['val', 'val', 'val_not_raise'])
assert len(w) == 2
# assert not list_sessions() # assert not list_sessions()

View file

@ -146,8 +146,8 @@ def test_slots_option_readonly():
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
q._requires = 'q' q._requires = 'q'
# assert not list_sessions() # assert not list_sessions()
#
#
#def test_slots_description(): #def test_slots_description():
# # __slots__ for OptionDescription should be complete for __getattr__ # # __slots__ for OptionDescription should be complete for __getattr__
# slots = set() # slots = set()

View file

@ -528,43 +528,43 @@ def test_submulti_unique():
# assert not list_sessions() # assert not list_sessions()
#def test_multi_submulti_meta(): def test_multi_submulti_meta():
# multi = StrOption('multi', '', multi=submulti) multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi]) od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg') cfg = Config(od1, name='cfg')
# cfg.property.read_write() cfg.property.read_write()
# cfg2 = Config(od1) cfg2 = Config(od1)
# cfg2.property.read_write() cfg2.property.read_write()
# meta = MetaConfig([cfg, cfg2]) meta = MetaConfig([cfg, cfg2])
# meta.property.read_write() meta.property.read_write()
# meta.option('multi').value.set([['val']]) meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']] assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg') newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]]) newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]] assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg') newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]] assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']] assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions() # assert not list_sessions()
#
#
#def test_multi_submulti_meta_no_cache(): def test_multi_submulti_meta_no_cache():
# multi = StrOption('multi', '', multi=submulti) multi = StrOption('multi', '', multi=submulti)
# multi = StrOption('multi', '', multi=submulti) multi = StrOption('multi', '', multi=submulti)
# od1 = OptionDescription('od', '', [multi]) od1 = OptionDescription('od', '', [multi])
# cfg = Config(od1, name='cfg') cfg = Config(od1, name='cfg')
# cfg.property.read_write() cfg.property.read_write()
# cfg2 = Config(od1) cfg2 = Config(od1)
# cfg.property.read_write() cfg.property.read_write()
# meta = MetaConfig([cfg, cfg2]) meta = MetaConfig([cfg, cfg2])
# meta.property.read_write() meta.property.read_write()
# meta.property.remove('cache') meta.property.remove('cache')
# meta.option('multi').value.set([['val']]) meta.option('multi').value.set([['val']])
# assert meta.option('multi').value.get() == [['val']] assert meta.option('multi').value.get() == [['val']]
# newcfg = meta.config('cfg') newcfg = meta.config('cfg')
# newcfg.option('multi').value.set([['val', None]]) newcfg.option('multi').value.set([['val', None]])
# assert cfg.option('multi').value.get() == [['val', None]] assert cfg.option('multi').value.get() == [['val', None]]
# newcfg = meta.config('cfg') newcfg = meta.config('cfg')
# assert newcfg.option('multi').value.get() == [['val', None]] assert newcfg.option('multi').value.get() == [['val', None]]
# assert meta.option('multi').value.get() == [['val']] assert meta.option('multi').value.get() == [['val']]
## assert not list_sessions() # assert not list_sessions()

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2017-2024 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2017-2025 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the
@ -52,6 +52,13 @@ from .autolib import Calculation
TIRAMISU_VERSION = 5 TIRAMISU_VERSION = 5
class Fake_SubConfig:
def __init__(self, config_bag, path, index):
self.config_bag = config_bag
self.path = path
self.index = index
class TiramisuHelp: class TiramisuHelp:
_tmpl_help = " {0}\t{1}" _tmpl_help = " {0}\t{1}"
@ -120,13 +127,16 @@ class CommonTiramisu(TiramisuHelp):
def _set_subconfig(self) -> None: def _set_subconfig(self) -> None:
if not self._subconfig: if not self._subconfig:
self._subconfig = self._config_bag.context.get_sub_config( try:
self._config_bag, self._subconfig = self._config_bag.context.get_sub_config(
self._path, self._config_bag,
self._index, self._path,
validate_properties=False, self._index,
allow_dynoption=self._allow_dynoption, validate_properties=False,
) allow_dynoption=self._allow_dynoption,
)
except AssertionError as err:
raise ConfigError(str(err))
def option_type(typ): def option_type(typ):
@ -202,7 +212,7 @@ def option_type(typ):
"please specify index with a follower option ({0}.{1})" "please specify index with a follower option ({0}.{1})"
).format(self.__class__.__name__, func.__name__) ).format(self.__class__.__name__, func.__name__)
raise ConfigError(msg) raise ConfigError(msg)
if self._validate_properties and "dont_validate_property" not in types: if 'validate_properties' in types or (self._validate_properties and "dont_validate_property" not in types):
settings = self._config_bag.context.get_settings() settings = self._config_bag.context.get_settings()
parent = self._subconfig.parent parent = self._subconfig.parent
if parent and parent.transitive_properties: if parent and parent.transitive_properties:
@ -253,10 +263,11 @@ class _TiramisuOptionWalk:
validate_properties: bool, validate_properties: bool,
*, *,
uncalculated: bool = False, uncalculated: bool = False,
with_index: bool = True,
): ):
options = [] options = []
for sub_subconfig in subconfig.get_children( for sub_subconfig in subconfig.get_children(
validate_properties, uncalculated=uncalculated validate_properties, uncalculated=uncalculated, with_index=with_index,
): ):
options.append( options.append(
TiramisuOption( TiramisuOption(
@ -287,7 +298,7 @@ class _TiramisuOptionOptionDescription:
"""Get Tiramisu option""" """Get Tiramisu option"""
return self._subconfig.option return self._subconfig.option
@option_type(["optiondescription", "option", "with_or_without_index", "symlink"]) @option_type(["optiondescription", "option", "with_or_without_index", "symlink", "allow_dynoption"])
def isoptiondescription(self): def isoptiondescription(self):
"""Test if option is an optiondescription""" """Test if option is an optiondescription"""
return self._subconfig.option.impl_is_optiondescription() return self._subconfig.option.impl_is_optiondescription()
@ -336,6 +347,15 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_getpath() return self._subconfig.option.impl_getpath()
return self._subconfig.true_path return self._subconfig.true_path
def parent(self):
parent = self._subconfig.parent
return TiramisuOption(
parent.path,
None,
self._config_bag,
subconfig=parent,
)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"]) @option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def has_dependency( def has_dependency(
self, self,
@ -345,18 +365,54 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_has_dependency(self_is_dep) return self._subconfig.option.impl_has_dependency(self_is_dep)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"]) @option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def dependencies(self): def dependencies(
self,
*,
uncalculated: bool = False,
):
"""Get dependencies from this option""" """Get dependencies from this option"""
options = [] options = []
for option in self._subconfig.option.get_dependencies(self._config_bag.context): context = self._config_bag.context
options.append( index = self._index
TiramisuOption( parent = self._subconfig.parent
option().impl_getpath(), parent_option = parent.option
None, for woption in self._subconfig.option.get_dependencies(self._config_bag.context):
self._config_bag, option = woption()
allow_dynoption=True, if not uncalculated and option.issubdyn():
) for subconfig in context.get_dynamic_from_dyn_option(self._subconfig, option):
) options.append(
TiramisuOption(
subconfig.path,
None,
self._config_bag,
)
)
elif not uncalculated and option.impl_is_dynoptiondescription():
for subconfig in context.get_dynamic_from_dyn_option(self._subconfig, option):
options.append(
TiramisuOption(
subconfig.path,
None,
self._config_bag,
)
)
else:
if not option.impl_is_optiondescription() and option.impl_is_follower() and parent_option.impl_is_leadership() and parent_option.in_same_leadership(option):
if index is not None:
current_indexes = [index]
else:
current_indexes = range(parent.get_length_leadership())
else:
current_indexes = [None]
for current_index in current_indexes:
options.append(
TiramisuOption(
option.impl_getpath(),
current_index,
self._config_bag,
allow_dynoption=uncalculated,
)
)
return options return options
@option_type(["option", "optiondescription", "symlink", "with_or_without_index"]) @option_type(["option", "optiondescription", "symlink", "with_or_without_index"])
@ -456,27 +512,27 @@ class _TiramisuOptionOptionDescription:
class _TiramisuOptionOption(_TiramisuOptionOptionDescription): class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
"""Manage option""" """Manage option"""
@option_type(["option", "symlink", "with_or_without_index"]) @option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def ismulti(self): def ismulti(self):
"""Test if option could have multi value""" """Test if option could have multi value"""
return self._subconfig.option.impl_is_multi() return self._subconfig.option.impl_is_multi()
@option_type(["option", "symlink", "with_or_without_index"]) @option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issubmulti(self): def issubmulti(self):
"""Test if option could have submulti value""" """Test if option could have submulti value"""
return self._subconfig.option.impl_is_submulti() return self._subconfig.option.impl_is_submulti()
@option_type(["option", "with_or_without_index", "symlink"]) @option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isleader(self): def isleader(self):
"""Test if option is a leader""" """Test if option is a leader"""
return self._subconfig.option.impl_is_leader() return self._subconfig.option.impl_is_leader()
@option_type(["option", "with_or_without_index", "symlink"]) @option_type(["option", "with_or_without_index", "symlink", "allow_dynoption"])
def isfollower(self): def isfollower(self):
"""Test if option is a follower""" """Test if option is a follower"""
return self._subconfig.option.impl_is_follower() return self._subconfig.option.impl_is_follower()
@option_type(["option", "symlink", "with_or_without_index"]) @option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def issymlinkoption(self) -> bool: def issymlinkoption(self) -> bool:
"""Test if option is a symlink option""" """Test if option is a symlink option"""
return self._subconfig.option.impl_is_symlinkoption() return self._subconfig.option.impl_is_symlinkoption()
@ -498,9 +554,11 @@ class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
return r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" return r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
@option_type(["option", "with_or_without_index", "symlink"]) @option_type(["option", "with_or_without_index", "symlink"])
def index(self): def index(self, index=None):
"""Get index of option""" """Get index of option"""
return self._subconfig.index if index is None:
return self._subconfig.index
return TiramisuOption(self._path, index, self._config_bag)
@option_type(["symlink", "optiondescription"]) @option_type(["symlink", "optiondescription"])
def option(self, *args, **kwargs): def option(self, *args, **kwargs):
@ -546,9 +604,9 @@ class TiramisuOptionOwner(CommonTiramisuOption):
_validate_properties = True _validate_properties = True
@option_type(["symlink", "option", "with_index"]) @option_type(["symlink", "option", "with_index"])
def get(self): def get(self, only_self=False):
"""Get owner for a specified option""" """Get owner for a specified option"""
return self._config_bag.context.get_owner(self._subconfig) return self._config_bag.context.get_owner(self._subconfig, validate_meta=not only_self)
@option_type(["symlink", "option", "with_index"]) @option_type(["symlink", "option", "with_index"])
def isdefault(self): def isdefault(self):
@ -597,6 +655,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
self._subconfig, self._subconfig,
uncalculated=uncalculated, uncalculated=uncalculated,
apply_requires=apply_requires, apply_requires=apply_requires,
not_unrestraint=True,
) )
@option_type(["option", "optiondescription", "with_or_without_index"]) @option_type(["option", "optiondescription", "with_or_without_index"])
@ -1056,12 +1115,13 @@ class TiramisuOption(
self._set_subconfig() self._set_subconfig()
return self._subconfig.option.impl_get_group_type() return self._subconfig.option.impl_get_group_type()
@option_type("optiondescription") @option_type(["optiondescription", "validate_properties"])
def list( def list(
self, self,
*, *,
validate_properties: bool = True, validate_properties: bool = True,
uncalculated: bool = False, uncalculated: bool = False,
with_index: bool = True,
): ):
"""List options inside an option description (by default list only option)""" """List options inside an option description (by default list only option)"""
self._set_subconfig() self._set_subconfig()
@ -1069,6 +1129,7 @@ class TiramisuOption(
self._subconfig, self._subconfig,
validate_properties, validate_properties,
uncalculated=uncalculated, uncalculated=uncalculated,
with_index=with_index,
) )
def _load_dict( def _load_dict(
@ -1189,6 +1250,8 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
self, self,
path: str, path: str,
value: Any, value: Any,
*,
index: Optional[int] = None,
only_config=undefined, only_config=undefined,
force_default=undefined, force_default=undefined,
force_default_if_same=undefined, force_default_if_same=undefined,
@ -1204,14 +1267,21 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
kwargs["force_default_if_same"] = force_default_if_same kwargs["force_default_if_same"] = force_default_if_same
if force_dont_change_value is not undefined: if force_dont_change_value is not undefined:
kwargs["force_dont_change_value"] = force_dont_change_value kwargs["force_dont_change_value"] = force_dont_change_value
option_bag = OptionBag( context = self._config_bag.context
None, if isinstance(context, KernelGroupConfig):
None, subconfig = Fake_SubConfig(self._config_bag,
self._config_bag, path,
path=path, index,
) )
else:
subconfig = context.get_sub_config(
self._config_bag,
path,
index,
validate_properties=False,
)
return self._config_bag.context.set_value( return self._config_bag.context.set_value(
option_bag, subconfig,
value, value,
**kwargs, **kwargs,
) )
@ -1599,6 +1669,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
*, *,
validate_properties: bool = True, validate_properties: bool = True,
uncalculated: bool = False, uncalculated: bool = False,
with_index: bool = True,
): ):
"""List options (by default list only option)""" """List options (by default list only option)"""
root = self._config_bag.context.get_root(self._config_bag) root = self._config_bag.context.get_root(self._config_bag)
@ -1606,6 +1677,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
root, root,
validate_properties, validate_properties,
uncalculated=uncalculated, uncalculated=uncalculated,
with_index=with_index,
) )
def _load_dict(self, clearable="all", remotable="minimum"): def _load_dict(self, clearable="all", remotable="minimum"):
@ -1912,7 +1984,7 @@ class Config(TiramisuAPI, TiramisuContextOption):
return f"<Config path=None>" return f"<Config path=None>"
class MetaConfig(TiramisuAPI): class MetaConfig(TiramisuAPI, TiramisuContextOption):
"""MetaConfig object that enables us to handle the sub configuration's options """MetaConfig object that enables us to handle the sub configuration's options
with common root optiondescription with common root optiondescription
""" """
@ -1980,7 +2052,7 @@ class MixConfig(TiramisuAPI):
display_name=display_name, display_name=display_name,
) )
settings = config.get_settings() settings = config.get_settings()
properties = settings.get_context_properties(config.properties_cache) properties = settings.get_context_properties()
permissives = settings.get_context_permissives() permissives = settings.get_context_permissives()
config_bag = ConfigBag( config_bag = ConfigBag(
config, config,

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the
@ -91,12 +91,12 @@ class CCache:
if option.issubdyn(): if option.issubdyn():
# it's an option in dynoptiondescription, remove cache for all generated option # it's an option in dynoptiondescription, remove cache for all generated option
self.reset_cache_dyn_option( self.reset_cache_dyn_option(
config_bag, subconfig,
option, option,
resetted_opts, resetted_opts,
) )
elif option.impl_is_dynoptiondescription(): elif option.impl_is_dynoptiondescription():
self._reset_cache_dyn_optiondescription( self.reset_cache_dyn_optiondescription(
option, option,
config_bag, config_bag,
resetted_opts, resetted_opts,
@ -120,13 +120,7 @@ class CCache:
resetted_opts, resetted_opts,
) )
def _reset_cache_dyn_optiondescription( def get_dynamic_from_dyn_optiondescription(self, config_bag, option):
self,
option,
config_bag,
resetted_opts,
):
# reset cache for all chidren
path = option.impl_getpath() path = option.impl_getpath()
if "." in path: if "." in path:
parent_path = path.rsplit(".", 1)[0] parent_path = path.rsplit(".", 1)[0]
@ -139,9 +133,21 @@ class CCache:
) )
else: else:
parent_subconfig = self.get_root(config_bag) parent_subconfig = self.get_root(config_bag)
for subconfig in parent_subconfig.dyn_to_subconfig( return parent_subconfig.dyn_to_subconfig(
option, option,
False, False,
)
def reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
):
# reset cache for all chidren
for subconfig in self.get_dynamic_from_dyn_optiondescription(
config_bag,
option,
): ):
self.reset_one_option_cache( self.reset_one_option_cache(
subconfig, subconfig,
@ -157,15 +163,20 @@ class CCache:
resetted_opts, resetted_opts,
) )
def reset_cache_dyn_option( def get_dynamic_from_dyn_option(self, subconfig, option):
self, config_bag = subconfig.config_bag
config_bag,
option,
resetted_opts,
):
currents = [self.get_root(config_bag)]
sub_paths = option.impl_getpath() sub_paths = option.impl_getpath()
for sub_path in sub_paths.split("."): current_paths = subconfig.path.split('.')
current_paths_max_index = len(current_paths) - 1
current_subconfigs = []
parent = subconfig
while True:
current_subconfigs.insert(0, parent)
parent = parent.parent
if parent.path is None:
break
currents = [self.get_root(config_bag)]
for idx, sub_path in enumerate(sub_paths.split(".")):
new_currents = [] new_currents = []
for current in currents: for current in currents:
sub_option = current.option.get_child( sub_option = current.option.get_child(
@ -175,15 +186,17 @@ class CCache:
allow_dynoption=True, allow_dynoption=True,
) )
if sub_option.impl_is_dynoptiondescription(): if sub_option.impl_is_dynoptiondescription():
new_currents.extend( if idx <= current_paths_max_index and sub_option == current_subconfigs[idx].option:
list( new_currents.append(current_subconfigs[idx])
current.dyn_to_subconfig( else:
sub_option, new_currents.extend(
False, list(
current.dyn_to_subconfig(
sub_option,
False,
)
) )
) )
)
else: else:
new_currents.append( new_currents.append(
current.get_child( current.get_child(
@ -194,7 +207,15 @@ class CCache:
), ),
) )
currents = new_currents currents = new_currents
for dyn_option_subconfig in currents: return currents
def reset_cache_dyn_option(
self,
subconfig,
option,
resetted_opts,
):
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,
@ -348,8 +369,9 @@ class SubConfig:
validate_properties, validate_properties,
*, *,
uncalculated: bool = False, uncalculated: bool = False,
with_index: bool = True,
): ):
if self.option.impl_is_leadership() and not uncalculated: if self.option.impl_is_leadership() and not uncalculated and with_index:
yield from self.get_leadership_children(validate_properties) yield from self.get_leadership_children(validate_properties)
else: else:
for child in self.option.get_children(): for child in self.option.get_children():
@ -536,6 +558,19 @@ class SubConfig:
return subconfigs return subconfigs
return subconfigs[0] return subconfigs[0]
def change_context(self, context) -> "SubConfig":
config_bag = self.config_bag.copy()
config_bag.context = context
return SubConfig(self.option,
self.index,
self.path,
config_bag,
self.parent,
self.identifiers,
true_path=self.true_path,
validate_properties=False,
)
class _Config(CCache): class _Config(CCache):
"""Sub configuration management entry. """Sub configuration management entry.
@ -816,6 +851,17 @@ class _Config(CCache):
# ============================================================================= # =============================================================================
# Manage value # Manage value
def set_value(self,
subconfig,
value: Any,
) -> Any:
"""set value
"""
self.get_settings().validate_properties(subconfig)
return self.get_values().set_value(subconfig,
value
)
def get_value( def get_value(
self, self,
subconfig, subconfig,
@ -935,6 +981,8 @@ class _Config(CCache):
def get_owner( def get_owner(
self, self,
subconfig: "SubConfig", subconfig: "SubConfig",
*,
validate_meta=True,
): ):
"""get owner""" """get owner"""
subconfigs = self._get( subconfigs = self._get(
@ -945,13 +993,14 @@ class _Config(CCache):
for sc in subconfigs: for sc in subconfigs:
owner = self.get_owner( owner = self.get_owner(
sc, sc,
validate_meta=validate_meta,
) )
if owner != owners.default: if owner != owners.default:
break break
else: else:
owner = owners.default owner = owners.default
else: else:
owner = self.get_values().getowner(subconfigs) owner = self.get_values().getowner(subconfigs, validate_meta=validate_meta)
return owner return owner
@ -1232,6 +1281,8 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=super-init-not-called # pylint: disable=super-init-not-called
names = [] names = []
for child in children: for child in children:
if not isinstance(child, (KernelConfig, KernelGroupConfig)):
raise TypeError(_("child must be a Config, GroupConfig, MixConfig or MetaConfig"))
name_ = child._impl_name name_ = child._impl_name
names.append(name_) names.append(name_)
if len(names) != len(set(names)): if len(names) != len(set(names)):
@ -1259,61 +1310,58 @@ class KernelGroupConfig(_CommonConfig):
def reset_cache( def reset_cache(
self, self,
option_bag, subconfig,
resetted_opts=None, resetted_opts=None,
): ):
if resetted_opts is None: if resetted_opts is None:
resetted_opts = [] resetted_opts = []
if isinstance(self, KernelMixConfig): if isinstance(self, KernelMixConfig):
super().reset_cache( super().reset_cache(
option_bag, subconfig,
resetted_opts=copy(resetted_opts), resetted_opts=copy(resetted_opts),
) )
for child in self._impl_children: for child in self._impl_children:
if option_bag is not None: if subconfig is not None:
coption_bag = option_bag.copy() parent_subconfig = subconfig.change_context(child)
cconfig_bag = coption_bag.config_bag.copy()
cconfig_bag.context = child
coption_bag.config_bag = cconfig_bag
else: else:
coption_bag = None parent_subconfig = None
child.reset_cache( child.reset_cache(
coption_bag, parent_subconfig,
resetted_opts=copy(resetted_opts), resetted_opts=copy(resetted_opts),
) )
def set_value( def set_value(
self, self,
option_bag, subconfig,
value, value,
only_config=False, only_config=False,
): ):
"""Setattr not in current KernelGroupConfig, but in each children""" """Setattr not in current KernelGroupConfig, but in each children"""
ret = [] ret = []
for child in self._impl_children: for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy() cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child cconfig_bag.context = child
if isinstance(child, KernelGroupConfig): if isinstance(child, KernelGroupConfig):
ret.extend( ret.extend(
child.set_value( child.set_value(
option_bag, subconfig,
value, value,
only_config=only_config, only_config=only_config,
) )
) )
else: else:
settings = child.get_settings() settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache) properties = settings.get_context_properties()
permissives = settings.get_context_permissives() permissives = settings.get_context_permissives()
cconfig_bag.properties = properties cconfig_bag.properties = properties
cconfig_bag.permissives = permissives cconfig_bag.permissives = permissives
try: try:
# GROUP # GROUP
coption_bag = child.get_sub_option_bag( coption_bag = child.get_sub_config(
cconfig_bag, cconfig_bag,
option_bag.path, subconfig.path,
option_bag.index, subconfig.index,
False, validate_properties=False,
) )
child.set_value( child.set_value(
coption_bag, coption_bag,
@ -1323,7 +1371,7 @@ class KernelGroupConfig(_CommonConfig):
# pylint: disable=protected-access # pylint: disable=protected-access
ret.append( ret.append(
PropertiesOptionError( PropertiesOptionError(
err._option_bag, err._subconfig,
err.proptype, err.proptype,
err._settings, err._settings,
err._opt_type, err._opt_type,
@ -1386,7 +1434,7 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag.context = child cconfig_bag.context = child
if cconfig_bag.properties is None: if cconfig_bag.properties is None:
settings = child.get_settings() settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache) properties = settings.get_context_properties()
permissives = settings.get_context_permissives() permissives = settings.get_context_permissives()
cconfig_bag.properties = properties cconfig_bag.properties = properties
cconfig_bag.permissives = permissives cconfig_bag.permissives = permissives
@ -1420,6 +1468,7 @@ class KernelGroupConfig(_CommonConfig):
def reset( def reset(
self, self,
path: str, path: str,
only_children: bool,
config_bag: ConfigBag, config_bag: ConfigBag,
) -> None: ) -> None:
"""reset value for specified path""" """reset value for specified path"""
@ -1428,19 +1477,19 @@ class KernelGroupConfig(_CommonConfig):
cconfig_bag = config_bag.copy() cconfig_bag = config_bag.copy()
cconfig_bag.context = child cconfig_bag.context = child
settings = child.get_settings() settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache) properties = settings.get_context_properties()
permissives = settings.get_context_permissives() permissives = settings.get_context_permissives()
cconfig_bag.properties = properties cconfig_bag.properties = properties
cconfig_bag.permissives = permissives cconfig_bag.permissives = permissives
cconfig_bag.remove_validation() cconfig_bag.remove_validation()
# GROUP # GROUP
option_bag = child.get_sub_option_bag( subconfig = child.get_sub_config(
cconfig_bag, cconfig_bag,
path, path,
None, None,
False, validate_properties=False,
)[-1] )
child.get_values().reset(option_bag) child.get_values().reset(subconfig)
def getconfig( def getconfig(
self, self,
@ -1495,7 +1544,7 @@ class KernelMixConfig(KernelGroupConfig):
def set_value( def set_value(
self, self,
option_bag, subconfig,
value, value,
only_config=False, only_config=False,
force_default=False, force_default=False,
@ -1526,10 +1575,10 @@ class KernelMixConfig(KernelGroupConfig):
) )
) )
for child in self._impl_children: for child in self._impl_children:
cconfig_bag = option_bag.config_bag.copy() cconfig_bag = subconfig.config_bag.copy()
cconfig_bag.context = child cconfig_bag.context = child
settings = child.get_settings() settings = child.get_settings()
properties = settings.get_context_properties(child.properties_cache) properties = settings.get_context_properties()
cconfig_bag.properties = properties cconfig_bag.properties = properties
cconfig_bag.permissives = settings.get_context_permissives() cconfig_bag.permissives = settings.get_context_permissives()
try: try:
@ -1541,14 +1590,14 @@ class KernelMixConfig(KernelGroupConfig):
not force_default and not force_default_if_same not force_default and not force_default_if_same
) )
# MIX # MIX
moption_bag = obj.get_sub_option_bag( moption_bag = obj.get_sub_config(
cconfig_bag, cconfig_bag,
option_bag.path, subconfig.path,
option_bag.index, subconfig.index,
validate_properties, validate_properties=validate_properties,
)[-1] )
if force_default_if_same: if force_default_if_same:
if not child.get_values().hasvalue(option_bag.path): if not child.get_values().hasvalue(subconfig.path, index=subconfig.index):
child_value = undefined child_value = undefined
else: else:
child_value = child.get_value(moption_bag) child_value = child.get_value(moption_bag)
@ -1568,7 +1617,7 @@ class KernelMixConfig(KernelGroupConfig):
# pylint: disable=protected-access # pylint: disable=protected-access
ret.append( ret.append(
PropertiesOptionError( PropertiesOptionError(
err._option_bag, err._subconfig,
err.proptype, err.proptype,
err._settings, err._settings,
err._opt_type, err._opt_type,
@ -1581,12 +1630,12 @@ class KernelMixConfig(KernelGroupConfig):
try: try:
# MIX # MIX
moption_bag = self.get_sub_option_bag( moption_bag = self.get_sub_config(
option_bag.config_bag, subconfig.config_bag,
option_bag.path, subconfig.path,
option_bag.index, subconfig.index,
not only_config, validate_properties=not only_config,
)[-1] )
if only_config: if only_config:
ret = super().set_value( ret = super().set_value(
moption_bag, moption_bag,
@ -1615,37 +1664,37 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag.remove_validation() rconfig_bag.remove_validation()
if self.impl_type == "meta": if self.impl_type == "meta":
# MIX # MIX
option_bag = self.get_sub_option_bag( subconfig = self.get_sub_config(
config_bag, config_bag,
path, path,
None, None,
True, validate_properties=True,
)[-1] )
elif not only_children: elif not only_children:
try: try:
# MIX # MIX
option_bag = self.get_sub_option_bag( subconfig = self.get_sub_config(
rconfig_bag, rconfig_bag,
path, path,
None, None,
True, validate_properties=True,
)[-1] )
except AttributeError: except AttributeError:
only_children = True only_children = True
for child in self._impl_children: for child in self._impl_children:
rconfig_bag.context = child rconfig_bag.context = child
try: try:
if self.impl_type == "meta": if self.impl_type == "meta":
moption_bag = option_bag moption_bag = subconfig
moption_bag.config_bag = rconfig_bag moption_bag.config_bag = rconfig_bag
else: else:
# MIX # MIX
moption_bag = child.get_sub_option_bag( moption_bag = child.get_sub_config(
rconfig_bag, rconfig_bag,
path, path,
None, None,
True, validate_properties=True,
)[-1] )
child.get_values().reset(moption_bag) child.get_values().reset(moption_bag)
except AttributeError: except AttributeError:
pass pass
@ -1656,8 +1705,8 @@ class KernelMixConfig(KernelGroupConfig):
rconfig_bag, rconfig_bag,
) )
if not only_children: if not only_children:
option_bag.config_bag = config_bag subconfig.config_bag = config_bag
self.get_values().reset(option_bag) self.get_values().reset(subconfig)
def new_config( def new_config(
self, self,

View file

@ -249,19 +249,19 @@ class LeadershipError(Exception):
option = subconfig.option option = subconfig.option
self.display_name = option.impl_get_display_name(subconfig, with_quote=True) self.display_name = option.impl_get_display_name(subconfig, with_quote=True)
self.code = code self.code = code
if prop: if prop is not None:
self.prop = prop self.prop = prop
if index: if index is not None:
self.index = index self.index = index
if length: if length is not None:
self.length = length self.length = length
if callback: if callback is not None:
self.callback = callback self.callback = callback
if args: if args is not None:
self.args = args self.args = args
if kwargs: if kwargs is not None:
self.kwargs = kwargs self.kwargs = kwargs
if ret: if ret is not None:
self.ret = ret self.ret = ret
def __str__(self): def __str__(self):
@ -310,6 +310,8 @@ class _CommonError:
self.display_type = display_type self.display_type = display_type
self.opt = weakref.ref(opt) self.opt = weakref.ref(opt)
self.name = opt.impl_get_display_name(subconfig, with_quote=True) self.name = opt.impl_get_display_name(subconfig, with_quote=True)
if subconfig:
self.path = subconfig.path
self.err_msg = err_msg self.err_msg = err_msg
self.index = index self.index = index
super().__init__(self.err_msg) super().__init__(self.err_msg)
@ -394,6 +396,12 @@ class CancelParam(Exception):
return False return False
class ValueErrorIndexes(ValueError):
def __init__(self, msg, indexes):
super().__init__(msg)
self.indexes = indexes
class Errors: class Errors:
@staticmethod @staticmethod
def raise_carry_out_calculation_error( def raise_carry_out_calculation_error(

View file

@ -145,7 +145,7 @@ class DynOptionDescription(OptionDescription):
) )
else: else:
values_.append(val) values_.append(val)
if __debug__ and len(values_) > len(set(values_)): if __debug__ and "demoting_error_warning" not in subconfig.config_bag.properties and len(values_) > len(set(values_)):
raise ValueError( raise ValueError(
_( _(
'DynOptionDescription "{0}" identifiers return a list with same values "{1}"' 'DynOptionDescription "{0}" identifiers return a list with same values "{1}"'

View file

@ -281,17 +281,14 @@ class Option(BaseOption):
force_index = None force_index = None
is_warnings_only = getattr(self, "_warnings_only", False) is_warnings_only = getattr(self, "_warnings_only", False)
def _is_not_unique(value): def _is_not_unique(current_value, values):
# if set(value) has not same length than value if current_value is None:
return
if not subconfig or not check_error or "unique" not in subconfig.properties: if not subconfig or not check_error or "unique" not in subconfig.properties:
return return
lvalue = [val for val in value if val is not None] indexes = [index for index, value in enumerate(values) if value == current_value]
if len(set(lvalue)) == len(lvalue): if len(indexes) > 1:
return raise ValueError(_('the value "{}" is not unique' "").format(current_value))
for idx, val in enumerate(value):
if val not in value[idx + 1 :]:
continue
raise ValueError(_('the value "{}" is not unique' "").format(val))
def calculation_validator( def calculation_validator(
val, val,
@ -394,80 +391,100 @@ class Option(BaseOption):
_index, _index,
) )
val = value
err_index = force_index err_index = force_index
try: ret = True
if not self.impl_is_multi(): if not self.impl_is_multi():
try:
do_validation( do_validation(
val, value,
None, None,
) )
elif force_index is not None: except ValueError as err:
if self.impl_is_submulti(): self.validate_parse_error(value, err_index, err, subconfig)
if not isinstance(value, list): ret = False
raise ValueError(_("which must be a list")) elif force_index is not None:
for val in value: if self.impl_is_submulti():
if not isinstance(value, list):
raise ValueError(_("which must be a list"))
for val in value:
try:
do_validation( do_validation(
val, val,
force_index, force_index,
) )
_is_not_unique(value) _is_not_unique(val, value)
else: except ValueError as err:
self.validate_parse_error(val, err_index, err, subconfig)
ret = False
else:
try:
do_validation( do_validation(
val, value,
force_index, force_index,
) )
elif isinstance(value, Calculation) and not subconfig: except ValueError as err:
pass self.validate_parse_error(value, err_index, err, subconfig)
elif self.impl_is_submulti(): ret = False
for err_index, lval in enumerate(value): elif isinstance(value, Calculation) and not subconfig:
if isinstance(lval, Calculation): pass
continue elif self.impl_is_submulti():
if not isinstance(lval, list): for err_index, lval in enumerate(value):
raise ValueError( if isinstance(lval, Calculation):
_('which "{}" must be a list of list' "").format(lval) continue
) if not isinstance(lval, list):
for val in lval: raise ValueError(
_('which "{}" must be a list of list' "").format(lval)
)
for val in lval:
try:
do_validation(val, err_index) do_validation(val, err_index)
_is_not_unique(lval) _is_not_unique(val, lval)
elif not isinstance(value, list): except ValueError as err:
raise ValueError(_("which must be a list")) self.validate_parse_error(val, err_index, err, subconfig)
else: ret = False
# FIXME suboptimal, not several time for whole=True! elif not isinstance(value, list):
for err_index, val in enumerate(value): raise ValueError(_("which must be a list"))
else:
# FIXME suboptimal, not several time for whole=True!
for err_index, val in enumerate(value):
try:
do_validation( do_validation(
val, val,
err_index, err_index,
) )
_is_not_unique(value) _is_not_unique(val, value)
except ValueError as err: except ValueError as err:
if ( self.validate_parse_error(val, err_index, err, subconfig)
not subconfig ret = False
or "demoting_error_warning" not in subconfig.config_bag.properties # return False
): return ret
raise ValueOptionError(
subconfig=subconfig, def validate_parse_error(self, val, index, err, subconfig):
val=val, if (
display_type=_(self.get_type()), not subconfig
opt=self, or "demoting_error_warning" not in subconfig.config_bag.properties
err_msg=str(err), ):
index=err_index, raise ValueOptionError(
) from err subconfig=subconfig,
warnings.warn_explicit( val=val,
ValueErrorWarning( display_type=_(self.get_type()),
subconfig=subconfig, opt=self,
val=val, err_msg=str(err),
display_type=_(self.get_type()), index=index,
opt=self, ) from err
err_msg=str(err), warnings.warn_explicit(
index=err_index, ValueErrorWarning(
), subconfig=subconfig,
ValueErrorWarning, val=val,
self.__class__.__name__, display_type=_(self.get_type()),
0, opt=self,
) err_msg=str(err),
return False index=index,
return True ),
ValueErrorWarning,
self.__class__.__name__,
0,
)
def validate_with_option( def validate_with_option(
self, self,

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"sets the options of the configuration objects Config object itself" "sets the options of the configuration objects Config object itself"
# Copyright (C) 2012-2024 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"takes care of the option's values and multi values" "takes care of the option's values and multi values"
# Copyright (C) 2013-2024 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2013-2025 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the
@ -189,10 +189,9 @@ class Values:
if subconfig.config_bag.context.impl_type == "config": if subconfig.config_bag.context.impl_type == "config":
return True return True
# it's a not a config, force to metaconfig only in *explicitly* set # it's a not a config, force to metaconfig only in *explicitly* set
return "force_metaconfig_on_freeze" in settings.get_stored_properties( return "force_metaconfig_on_freeze" in settings.get_personalize_properties(
subconfig.path, subconfig.path,
subconfig.index, subconfig.index,
frozenset(),
) )
return False return False
@ -434,36 +433,28 @@ class Values:
If not found, return None If not found, return None
For follower option, return the Config where leader is modified For follower option, return the Config where leader is modified
""" """
def build_option_bag(subconfig, parent):
doption_bag = subconfig.copy()
config_bag = subconfig.config_bag.copy()
config_bag.context = parent
config_bag.unrestraint()
doption_bag.config_bag = config_bag
return doption_bag
for parent in subconfig.config_bag.context.get_parents(): for parent in subconfig.config_bag.context.get_parents():
doption_bag = build_option_bag(subconfig, parent) parent_subconfig = subconfig.change_context(parent)
parent_subconfig.config_bag.unrestraint()
parent_subconfig.properties = subconfig.properties
if "force_metaconfig_on_freeze" in subconfig.properties: if "force_metaconfig_on_freeze" in subconfig.properties:
# remove force_metaconfig_on_freeze only if option in metaconfig # remove force_metaconfig_on_freeze only if option in metaconfig
# hasn't force_metaconfig_on_freeze properties # hasn't force_metaconfig_on_freeze properties
ori_properties = doption_bag.properties ori_properties = parent_subconfig.properties
settings = doption_bag.config_bag.context.get_settings() settings = parent_subconfig.config_bag.context.get_settings()
doption_bag.properties = settings.getproperties(doption_bag) parent_subconfig.properties = settings.getproperties(parent_subconfig)
if not self.check_force_to_metaconfig(doption_bag): if not self.check_force_to_metaconfig(parent_subconfig):
doption_bag.properties = ori_properties - { parent_subconfig.properties = ori_properties - {
"force_metaconfig_on_freeze" "force_metaconfig_on_freeze"
} }
else: else:
doption_bag.properties = ori_properties parent_subconfig.properties = ori_properties
parent_owner = parent.get_values().getowner( parent_owner = parent.get_values().getowner(
doption_bag, parent_subconfig,
parent,
only_default=True, only_default=True,
) )
if parent_owner != owners.default: if parent_owner != owners.default:
return doption_bag return parent_subconfig
return None return None
@ -517,9 +508,6 @@ class Values:
was present was present
:returns: a `setting.owners.Owner` object :returns: a `setting.owners.Owner` object
""" """
# context = subconfig.config_bag.context
# settings = context.get_settings()
# settings.validate_properties(subconfig)
if ( if (
"frozen" in subconfig.properties "frozen" in subconfig.properties
and "force_default_on_freeze" in subconfig.properties and "force_default_on_freeze" in subconfig.properties
@ -548,11 +536,10 @@ class Values:
values = msubconfig.config_bag.context.get_values() values = msubconfig.config_bag.context.get_values()
owner = values.getowner( owner = values.getowner(
msubconfig, msubconfig,
parent,
only_default=only_default, only_default=only_default,
) )
elif "force_metaconfig_on_freeze" in subconfig.properties: elif "force_metaconfig_on_freeze" in subconfig.properties:
return owners.default owner = owners.default
return owner return owner
def set_owner( def set_owner(
@ -749,8 +736,8 @@ class Values:
if index >= length: if index >= length:
raise IndexError( raise IndexError(
_( _(
"index {index} is greater than the length {length} " f"index {index} is greater than the length {length} "
"for option {subconfig.option.impl_get_display_name(with_quote=True)}" f"for option {subconfig.option.impl_get_display_name(subconfig, with_quote=True)}"
) )
) )
current_value.pop(index) current_value.pop(index)