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
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
msgid "which must not be a list"

View file

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,95 +15,95 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: tiramisu/api.py:79
#: tiramisu/api.py:86
msgid "Settings:"
msgstr ""
#: tiramisu/api.py:83
#: tiramisu/api.py:90
msgid "Access to option without verifying permissive properties"
msgstr ""
#: tiramisu/api.py:88
#: tiramisu/api.py:95
msgid "Access to option without property restriction"
msgstr ""
#: tiramisu/api.py:93
#: tiramisu/api.py:100
msgid "Do not warnings during validation"
msgstr ""
#: tiramisu/api.py:97
#: tiramisu/api.py:104
msgid "Commands:"
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})"
msgstr ""
#: tiramisu/api.py:196
#: tiramisu/api.py:206
msgid "please do not specify index ({0}.{1})"
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})"
msgstr ""
#: tiramisu/api.py:222
#: tiramisu/api.py:232
msgid "please specify a valid sub function ({0}.{1}): {2}"
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"
msgstr ""
#: tiramisu/api.py:532
#: tiramisu/api.py:581
msgid "cannot get option from a follower symlink without index"
msgstr ""
#: tiramisu/api.py:607
#: tiramisu/api.py:657
msgid "cannot add this property: \"{0}\""
msgstr ""
#: tiramisu/api.py:634
#: tiramisu/api.py:684
msgid "cannot remove option's property \"{0}\", use permissive instead in option \"{1}\""
msgstr ""
#: tiramisu/api.py:638
#: tiramisu/api.py:688
msgid "cannot find \"{0}\" in option \"{1}\""
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}\""
msgstr ""
#: tiramisu/api.py:647
#: tiramisu/api.py:697
msgid "cannot find \"{0}\" in option \"{1}\" at index \"{2}\""
msgstr ""
#: tiramisu/api.py:691
#: tiramisu/api.py:741
msgid "cannot find \"{0}\""
msgstr ""
#: tiramisu/api.py:873
#: tiramisu/api.py:923
msgid "only multi value has defaultmulti"
msgstr ""
#: tiramisu/api.py:1037
#: tiramisu/api.py:1087
msgid "please specify a valid sub function ({0}.{1}) for {2}"
msgstr ""
#: tiramisu/api.py:1424
#: tiramisu/api.py:1485
msgid "properties must be a frozenset"
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)"
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 {}"
msgstr ""
#: tiramisu/api.py:1829
#: tiramisu/api.py:1892
msgid "do not use unrestraint, nowarnings or forcepermissive together"
msgstr ""
@ -207,79 +207,83 @@ msgstr ""
msgid "unexpected error \"{1}\" in function \"{2}\" for option {0}"
msgstr ""
#: tiramisu/config.py:574
#: tiramisu/config.py:609
msgid "there is no option description for this config (may be GroupConfig)"
msgstr ""
#: tiramisu/config.py:663
#: tiramisu/config.py:698
msgid "no option found in config with these criteria"
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"
msgstr ""
#: tiramisu/config.py:1140
#: tiramisu/config.py:1186
msgid "parent of {0} not already exists"
msgstr ""
#: tiramisu/config.py:1187
#: tiramisu/config.py:1233
msgid "cannot set leadership object has root optiondescription"
msgstr ""
#: tiramisu/config.py:1190
#: tiramisu/config.py:1236
msgid "cannot set dynoptiondescription object has root optiondescription"
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}\""
msgstr ""
#: tiramisu/config.py:1453
#: tiramisu/config.py:1499
msgid "unknown config \"{}\""
msgstr ""
#: tiramisu/config.py:1478
#: tiramisu/config.py:1524
msgid "child must be a Config, MixConfig or MetaConfig"
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"
msgstr ""
#: tiramisu/config.py:1523
#: tiramisu/config.py:1569
msgid "force_default and force_dont_change_value cannot be set together"
msgstr ""
#: tiramisu/config.py:1672
#: tiramisu/config.py:1718
msgid "config name must be uniq in groupconfig for {0}"
msgstr ""
#: tiramisu/config.py:1717
#: tiramisu/config.py:1763
msgid "config added has no name, the name is mandatory"
msgstr ""
#: tiramisu/config.py:1722
#: tiramisu/config.py:1768
msgid "config name \"{0}\" is not uniq in groupconfig \"{1}\""
msgstr ""
#: tiramisu/config.py:1740 tiramisu/config.py:1746
#: tiramisu/config.py:1786 tiramisu/config.py:1792
msgid "cannot find the config {0}"
msgstr ""
#: tiramisu/config.py:1772
#: tiramisu/config.py:1818
msgid "MetaConfig with optiondescription must have string has child, not {}"
msgstr ""
#: tiramisu/config.py:1784
#: tiramisu/config.py:1830
msgid "child must be a Config or MetaConfig"
msgstr ""
#: tiramisu/config.py:1789
#: tiramisu/config.py:1835
msgid "all config in metaconfig must have the same optiondescription"
msgstr ""
#: tiramisu/config.py:1806
#: tiramisu/config.py:1852
msgid "metaconfig must have the same optiondescription"
msgstr ""
@ -391,23 +395,23 @@ msgstr ""
msgid "the \"{0}\" function must not return a list (\"{1}\") for the follower option {2}"
msgstr ""
#: tiramisu/error.py:331
#: tiramisu/error.py:333
msgid "invalid value"
msgstr ""
#: tiramisu/error.py:341
#: tiramisu/error.py:343
msgid "attention, \"{0}\" could be an invalid {1} for {2}"
msgstr ""
#: tiramisu/error.py:345
#: tiramisu/error.py:347
msgid "attention, \"{0}\" could be an invalid {1} for {2} at index \"{3}\""
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}"
msgstr ""
#: tiramisu/error.py:368
#: tiramisu/error.py:370
msgid "\"{0}\" is an invalid {1} for {2} at index \"{3}\""
msgstr ""
@ -499,19 +503,19 @@ msgstr ""
msgid "invalid string"
msgstr ""
#: tiramisu/option/choiceoption.py:47
#: tiramisu/option/choiceoption.py:52
msgid "values must be a tuple or a calculation for {0}"
msgstr ""
#: tiramisu/option/choiceoption.py:70
#: tiramisu/option/choiceoption.py:75
msgid "the calculated values \"{0}\" for \"{1}\" is not a list"
msgstr ""
#: tiramisu/option/choiceoption.py:101
#: tiramisu/option/choiceoption.py:106
msgid "only \"{0}\" is allowed"
msgstr ""
#: tiramisu/option/choiceoption.py:103
#: tiramisu/option/choiceoption.py:108
msgid "only {0} are allowed"
msgstr ""
@ -728,19 +732,19 @@ msgstr ""
msgid "invalid default_multi value \"{0}\" for option {1}, must be a list for a submulti"
msgstr ""
#: tiramisu/option/option.py:294
#: tiramisu/option/option.py:291
msgid "the value \"{}\" is not unique"
msgstr ""
#: tiramisu/option/option.py:356
#: tiramisu/option/option.py:353
msgid "which must not be a list"
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"
msgstr ""
#: tiramisu/option/option.py:428
#: tiramisu/option/option.py:436
msgid "which \"{}\" must be a list of list"
msgstr ""
@ -805,27 +809,27 @@ msgstr ""
msgid "too weak"
msgstr ""
#: tiramisu/option/portoption.py:80
#: tiramisu/option/portoption.py:77
msgid "inconsistency in allowed range"
msgstr ""
#: tiramisu/option/portoption.py:85
#: tiramisu/option/portoption.py:82
msgid "max value is empty"
msgstr ""
#: tiramisu/option/portoption.py:98
#: tiramisu/option/portoption.py:95
msgid "range must have two values only"
msgstr ""
#: tiramisu/option/portoption.py:101
#: tiramisu/option/portoption.py:98
msgid "first port in range must be smaller than the second one"
msgstr ""
#: tiramisu/option/portoption.py:127
#: tiramisu/option/portoption.py:124
msgid "should be between {0} and {1}"
msgstr ""
#: tiramisu/option/portoption.py:129
#: tiramisu/option/portoption.py:126
msgid "must be between {0} and {1}"
msgstr ""
@ -905,19 +909,15 @@ msgstr ""
msgid "unknown action {}"
msgstr ""
#: tiramisu/value.py:570 tiramisu/value.py:872
#: tiramisu/value.py:557 tiramisu/value.py:859
msgid "set owner \"{0}\" is forbidden"
msgstr ""
#: tiramisu/value.py:577
#: tiramisu/value.py:564
msgid "\"{0}\" is a default value, so we cannot change owner to \"{1}\""
msgstr ""
#: tiramisu/value.py:751
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
#: tiramisu/value.py:845
msgid "information's item not found \"{}\""
msgstr ""

View file

@ -1,5 +1,4 @@
# from json import dumps, loads
import asyncio
from os import environ
try:
from tiramisu_api import Config
@ -63,3 +62,14 @@ def parse_od_get(dico):
else:
ret[k.path()] = v
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()
#def test_choiceoption_calc_opt_multi_function(config_type):
def test_choiceoption_calc_opt_multi_function():
# FIXME
config_type = 'tiramisu'

View file

@ -13,12 +13,6 @@ from tiramisu.error import PropertiesOptionError, ValueWarning, ConfigError
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():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
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 conf2.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert not list_sessions()
#
#
#def test_copy_force_store_value_metaconfig():
# od1 = make_description()
# meta = MetaConfig([], optiondescription=od1)
# conf = meta.config.new()
# assert meta.property.get() == conf.property.get()
# assert meta.permissive.get() == conf.permissive.get()
# conf.property.read_write()
# assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
# assert meta.value.exportation() == {}
## assert not list_sessions()
def test_copy_force_store_value_metaconfig():
od1 = make_description()
meta = MetaConfig([], optiondescription=od1)
conf = meta.config.new()
assert meta.property.get() == conf.property.get()
assert meta.permissive.get() == conf.permissive.get()
conf.property.read_write()
assert conf.value.exportation() == {'creole.general.wantref': {None: [False, 'forced']}}
assert meta.value.exportation() == {}
# assert not list_sessions()

View file

@ -1,7 +1,7 @@
# coding: utf-8
from .autopath import do_autopath
do_autopath()
from .config import parse_od_get
from .config import parse_od_get, get_dependencies
import pytest
from tiramisu.setting import groups, owners
@ -336,6 +336,7 @@ def test_prop_dyndescription_force_store_value_calculation_prefix():
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
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.dodval2.st').owner.isdefault() == False
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])
od2 = OptionDescription('od', '', [od, lst])
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']}
cfg.option('od.dodval1.st').value.set('val1')
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']}
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 get_dependencies(cfg.option('od.out')) == [('od.dodval1.st', None), ('od.dodval2.st', None)]
# assert not list_sessions()
@ -1191,6 +1196,8 @@ def test_leadership_dyndescription():
cfg = Config(od1)
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 cfg.option('od.stval1.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])
cfg = Config(od1)
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.stval2.st1.st1').owner.isdefault() == False
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()
#def test_follower_properties():
def test_follower_properties():
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',))
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
#def teardown_function(function):
# assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__)
def is_mandatory(variable):
return True
@ -688,25 +686,25 @@ def return_list(val=None, identifier=None):
return ['val1', 'val2']
#def test_mandatory_dyndescription():
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
# od = OptionDescription('od', '', [dod])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
#
#
#def test_mandatory_dyndescription_context():
# val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
# st = StrOption('st', '', properties=('mandatory',))
# dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
# od = OptionDescription('od', '', [dod, val1])
# od2 = OptionDescription('od', '', [od])
# cfg = Config(od2)
# cfg.property.read_only()
# compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription():
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list))
od = OptionDescription('od', '', [dod])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
def test_mandatory_dyndescription_context():
val1 = StrOption('val1', '', ['val1', 'val2'], multi=True)
st = StrOption('st', '', properties=('mandatory',))
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(val1))))
od = OptionDescription('od', '', [dod, val1])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
cfg.property.read_only()
compare(cfg.value.mandatory(), ['od.dodval1.st', 'od.dodval2.st'])
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')
#def test_multi_parents_path():
# """
# metacfg1 (1) ---
# | -- cfg1
# metacfg2 (2) ---
# """
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert metacfg1.config.path() == 'metacfg1'
# assert metacfg2.config.path() == 'metacfg2'
# assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
#
#
#def test_multi_parents_path_same():
# """
# --- metacfg2 (1) ---
# metacfg1 --| | -- cfg1
# --- metacfg3 (2) ---
# """
# metacfg1 = make_metaconfig()
# metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
# metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
# cfg1 = metacfg2.config.new(type='config', name="cfg1")
# metacfg3.config.add(cfg1)
# #
# assert metacfg2.config.path() == 'metacfg1.metacfg2'
# assert metacfg3.config.path() == 'metacfg1.metacfg3'
# assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
# metacfg1.option('od1.i1').value.set(1)
# metacfg3.option('od1.i1').value.set(2)
# assert cfg1.option('od1.i1').value.get() == 1
# orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
# deep = orideep
# while True:
# try:
# children = list(deep.config.list())
# except:
# break
# assert len(children) < 2
# deep = children[0]
# assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
# assert cfg1.option('od1.i1').value.get() == 1
#
#
#
#def test_multi_parents_value():
# metacfg1 = make_metaconfig()
# cfg1 = metacfg1.config.new(type='config', name="cfg1")
# metacfg2 = MetaConfig([cfg1], name='metacfg2')
# #
# assert cfg1.option('od1.i1').value.get() == None
# assert cfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i3').value.get() == None
# #
# assert metacfg1.option('od1.i1').value.get() == None
# assert metacfg1.option('od1.i2').value.get() == 1
# assert metacfg1.option('od1.i3').value.get() == None
# #
# assert metacfg2.option('od1.i1').value.get() == None
# assert metacfg2.option('od1.i2').value.get() == 1
# assert metacfg2.option('od1.i3').value.get() == None
# #
# metacfg1.option('od1.i3').value.set(3)
# assert metacfg1.option('od1.i3').value.get() == 3
# assert cfg1.option('od1.i3').value.get() == 3
# assert metacfg2.option('od1.i2').value.get() == 1
# #
# metacfg2.option('od1.i2').value.set(4)
# assert metacfg2.option('od1.i2').value.get() == 4
# assert metacfg1.option('od1.i2').value.get() == 1
# assert cfg1.option('od1.i2').value.get() == 4
def test_multi_parents_path():
"""
metacfg1 (1) ---
| -- cfg1
metacfg2 (2) ---
"""
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert metacfg1.config.path() == 'metacfg1'
assert metacfg2.config.path() == 'metacfg2'
assert cfg1.config.path() == 'metacfg2.metacfg1.cfg1'
def test_multi_parents_path_same():
"""
--- metacfg2 (1) ---
metacfg1 --| | -- cfg1
--- metacfg3 (2) ---
"""
metacfg1 = make_metaconfig()
metacfg2 = metacfg1.config.new(type='metaconfig', name="metacfg2")
metacfg3 = metacfg1.config.new(type='metaconfig', name="metacfg3")
cfg1 = metacfg2.config.new(type='config', name="cfg1")
metacfg3.config.add(cfg1)
#
assert metacfg2.config.path() == 'metacfg1.metacfg2'
assert metacfg3.config.path() == 'metacfg1.metacfg3'
assert cfg1.config.path() == 'metacfg1.metacfg3.metacfg1.metacfg2.cfg1'
metacfg1.option('od1.i1').value.set(1)
metacfg3.option('od1.i1').value.set(2)
assert cfg1.option('od1.i1').value.get() == 1
orideep = cfg1.config.deepcopy(metaconfig_prefix="test_", name='test_cfg1')
deep = orideep
while True:
try:
children = list(deep.config.list())
except:
break
assert len(children) < 2
deep = children[0]
assert deep.config.path() == 'test_metacfg3.test_metacfg1.test_metacfg2.test_cfg1'
assert cfg1.option('od1.i1').value.get() == 1
def test_multi_parents_value():
metacfg1 = make_metaconfig()
cfg1 = metacfg1.config.new(type='config', name="cfg1")
metacfg2 = MetaConfig([cfg1], name='metacfg2')
#
assert cfg1.option('od1.i1').value.get() == None
assert cfg1.option('od1.i2').value.get() == 1
assert cfg1.option('od1.i3').value.get() == None
#
assert metacfg1.option('od1.i1').value.get() == None
assert metacfg1.option('od1.i2').value.get() == 1
assert metacfg1.option('od1.i3').value.get() == None
#
assert metacfg2.option('od1.i1').value.get() == None
assert metacfg2.option('od1.i2').value.get() == 1
assert metacfg2.option('od1.i3').value.get() == None
#
metacfg1.option('od1.i3').value.set(3)
assert metacfg1.option('od1.i3').value.get() == 3
assert cfg1.option('od1.i3').value.get() == 3
assert metacfg2.option('od1.i2').value.get() == 1
#
metacfg2.option('od1.i2').value.set(4)
assert metacfg2.option('od1.i2').value.get() == 4
assert metacfg1.option('od1.i2').value.get() == 1
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
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
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():
@ -693,6 +693,7 @@ def test_callback_leader_and_followers_leader(config_type):
cfg = Config(od1)
cfg.property.read_write()
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('val2.val2').value.get() == ['val']
#
@ -783,10 +784,29 @@ def test_callback_leader_and_followers_leader2(config_type):
cfg = Config(od1)
cfg.property.read_write()
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'])
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.val3', 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()
@ -1499,6 +1519,7 @@ def test_leadership_callback_description(config_type):
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.st2', 0).owner.get() == owner
assert get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None)]
# assert not list_sessions()
@ -1515,6 +1536,8 @@ def test_leadership_callback_outside(config_type):
owner = cfg.owner.get()
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 get_dependencies(cfg.option('od.st.st1.st2')) == [('od.st.st1', None), ('od.st.st3', None,)]
## assert not list_sessions()

View file

@ -154,8 +154,8 @@ def test_force_default_on_freeze_multi():
# with pytest.raises(ConfigError):
# Config(od1)
# assert not list_sessions()
#
#
#def test_force_metaconfig_on_freeze_leader():
# dummy1 = BoolOption('dummy1', 'Test int option', multi=True, properties=('force_metaconfig_on_freeze',))
# 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()
#def test_importation_force_store_value():
# gcdummy = BoolOption('dummy', 'dummy', default=False,
# properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# config1 = Config(od1)
# assert config1.value.exportation() == {}
# config1.property.add('frozen')
# assert config1.value.exportation() == {}
# config1.property.add('force_store_value')
# assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# exportation = config1.property.exportation()
# config2 = Config(od1)
# assert config2.value.exportation() == {}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# config2.property.importation(exportation)
# assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
## assert not list_sessions()
#
#
def test_importation_force_store_value():
gcdummy = BoolOption('dummy', 'dummy', default=False,
properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
od1 = OptionDescription('tiramisu', '', [gcgroup])
config1 = Config(od1)
assert config1.value.exportation() == {}
config1.property.add('frozen')
assert config1.value.exportation() == {}
config1.property.add('force_store_value')
assert config1.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
exportation = config1.property.exportation()
config2 = Config(od1)
assert config2.value.exportation() == {}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
config2.property.importation(exportation)
assert config2.value.exportation() == {'gc.dummy': {None: [False, 'forced']}}
# assert not list_sessions()
def test_set_modified_value():
gcdummy = BoolOption('dummy', 'dummy', default=False, properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy])
@ -920,18 +920,18 @@ def test_set_modified_value():
# assert not list_sessions()
#def test_none_is_not_modified():
# gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
# gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
# gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
# od1 = OptionDescription('tiramisu', '', [gcgroup])
# cfg = Config(od1)
# assert cfg.value.exportation() == {}
# cfg.property.read_write()
# assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
## assert not list_sessions()
#
#
def test_none_is_not_modified():
gcdummy = StrOption('dummy', 'dummy', properties=('force_store_value',))
gcdummy1 = StrOption('dummy1', 'dummy1', default="str", properties=('force_store_value',))
gcgroup = OptionDescription('gc', '', [gcdummy, gcdummy1])
od1 = OptionDescription('tiramisu', '', [gcgroup])
cfg = Config(od1)
assert cfg.value.exportation() == {}
cfg.property.read_write()
assert cfg.value.exportation() == {'gc.dummy1': {None: ['str', 'forced']}}
# assert not list_sessions()
def test_pprint():
msg_error = _("cannot access to {0} {1} because has {2} {3}")
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):
if value != 'val':
if value not in ['val', 'val_not_raise']:
raise ValueError('test error')
@ -341,6 +341,13 @@ def test_validator_multi(config_type):
with warnings.catch_warnings(record=True) as w:
cfg.option('opt1').value.set(['val', 'val1'])
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()

View file

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

View file

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

View file

@ -1,5 +1,5 @@
# -*- 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
# 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
class Fake_SubConfig:
def __init__(self, config_bag, path, index):
self.config_bag = config_bag
self.path = path
self.index = index
class TiramisuHelp:
_tmpl_help = " {0}\t{1}"
@ -120,13 +127,16 @@ class CommonTiramisu(TiramisuHelp):
def _set_subconfig(self) -> None:
if not self._subconfig:
self._subconfig = self._config_bag.context.get_sub_config(
self._config_bag,
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
)
try:
self._subconfig = self._config_bag.context.get_sub_config(
self._config_bag,
self._path,
self._index,
validate_properties=False,
allow_dynoption=self._allow_dynoption,
)
except AssertionError as err:
raise ConfigError(str(err))
def option_type(typ):
@ -202,7 +212,7 @@ def option_type(typ):
"please specify index with a follower option ({0}.{1})"
).format(self.__class__.__name__, func.__name__)
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()
parent = self._subconfig.parent
if parent and parent.transitive_properties:
@ -253,10 +263,11 @@ class _TiramisuOptionWalk:
validate_properties: bool,
*,
uncalculated: bool = False,
with_index: bool = True,
):
options = []
for sub_subconfig in subconfig.get_children(
validate_properties, uncalculated=uncalculated
validate_properties, uncalculated=uncalculated, with_index=with_index,
):
options.append(
TiramisuOption(
@ -287,7 +298,7 @@ class _TiramisuOptionOptionDescription:
"""Get Tiramisu 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):
"""Test if option is an optiondescription"""
return self._subconfig.option.impl_is_optiondescription()
@ -336,6 +347,15 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_getpath()
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"])
def has_dependency(
self,
@ -345,18 +365,54 @@ class _TiramisuOptionOptionDescription:
return self._subconfig.option.impl_has_dependency(self_is_dep)
@option_type(["optiondescription", "option", "symlink", "with_or_without_index"])
def dependencies(self):
def dependencies(
self,
*,
uncalculated: bool = False,
):
"""Get dependencies from this option"""
options = []
for option in self._subconfig.option.get_dependencies(self._config_bag.context):
options.append(
TiramisuOption(
option().impl_getpath(),
None,
self._config_bag,
allow_dynoption=True,
)
)
context = self._config_bag.context
index = self._index
parent = self._subconfig.parent
parent_option = parent.option
for woption in self._subconfig.option.get_dependencies(self._config_bag.context):
option = woption()
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
@option_type(["option", "optiondescription", "symlink", "with_or_without_index"])
@ -456,27 +512,27 @@ class _TiramisuOptionOptionDescription:
class _TiramisuOptionOption(_TiramisuOptionOptionDescription):
"""Manage option"""
@option_type(["option", "symlink", "with_or_without_index"])
@option_type(["option", "symlink", "with_or_without_index", "allow_dynoption"])
def ismulti(self):
"""Test if option could have multi value"""
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):
"""Test if option could have submulti value"""
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):
"""Test if option is a 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):
"""Test if option is a 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:
"""Test if option is a symlink option"""
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]?)$"
@option_type(["option", "with_or_without_index", "symlink"])
def index(self):
def index(self, index=None):
"""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"])
def option(self, *args, **kwargs):
@ -546,9 +604,9 @@ class TiramisuOptionOwner(CommonTiramisuOption):
_validate_properties = True
@option_type(["symlink", "option", "with_index"])
def get(self):
def get(self, only_self=False):
"""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"])
def isdefault(self):
@ -597,6 +655,7 @@ class TiramisuOptionProperty(CommonTiramisuOption):
self._subconfig,
uncalculated=uncalculated,
apply_requires=apply_requires,
not_unrestraint=True,
)
@option_type(["option", "optiondescription", "with_or_without_index"])
@ -1056,12 +1115,13 @@ class TiramisuOption(
self._set_subconfig()
return self._subconfig.option.impl_get_group_type()
@option_type("optiondescription")
@option_type(["optiondescription", "validate_properties"])
def list(
self,
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options inside an option description (by default list only option)"""
self._set_subconfig()
@ -1069,6 +1129,7 @@ class TiramisuOption(
self._subconfig,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(
@ -1189,6 +1250,8 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
self,
path: str,
value: Any,
*,
index: Optional[int] = None,
only_config=undefined,
force_default=undefined,
force_default_if_same=undefined,
@ -1204,14 +1267,21 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
kwargs["force_default_if_same"] = force_default_if_same
if force_dont_change_value is not undefined:
kwargs["force_dont_change_value"] = force_dont_change_value
option_bag = OptionBag(
None,
None,
self._config_bag,
path=path,
)
context = self._config_bag.context
if isinstance(context, KernelGroupConfig):
subconfig = Fake_SubConfig(self._config_bag,
path,
index,
)
else:
subconfig = context.get_sub_config(
self._config_bag,
path,
index,
validate_properties=False,
)
return self._config_bag.context.set_value(
option_bag,
subconfig,
value,
**kwargs,
)
@ -1599,6 +1669,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
*,
validate_properties: bool = True,
uncalculated: bool = False,
with_index: bool = True,
):
"""List options (by default list only option)"""
root = self._config_bag.context.get_root(self._config_bag)
@ -1606,6 +1677,7 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
root,
validate_properties,
uncalculated=uncalculated,
with_index=with_index,
)
def _load_dict(self, clearable="all", remotable="minimum"):
@ -1912,7 +1984,7 @@ class Config(TiramisuAPI, TiramisuContextOption):
return f"<Config path=None>"
class MetaConfig(TiramisuAPI):
class MetaConfig(TiramisuAPI, TiramisuContextOption):
"""MetaConfig object that enables us to handle the sub configuration's options
with common root optiondescription
"""
@ -1980,7 +2052,7 @@ class MixConfig(TiramisuAPI):
display_name=display_name,
)
settings = config.get_settings()
properties = settings.get_context_properties(config.properties_cache)
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
config_bag = ConfigBag(
config,

View file

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

View file

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

View file

@ -145,7 +145,7 @@ class DynOptionDescription(OptionDescription):
)
else:
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(
_(
'DynOptionDescription "{0}" identifiers return a list with same values "{1}"'

View file

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

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"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
# under the terms of the GNU Lesser General Public License as published by the

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"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
# 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":
return True
# 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.index,
frozenset(),
)
return False
@ -434,36 +433,28 @@ class Values:
If not found, return None
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():
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:
# remove force_metaconfig_on_freeze only if option in metaconfig
# hasn't force_metaconfig_on_freeze properties
ori_properties = doption_bag.properties
settings = doption_bag.config_bag.context.get_settings()
doption_bag.properties = settings.getproperties(doption_bag)
if not self.check_force_to_metaconfig(doption_bag):
doption_bag.properties = ori_properties - {
ori_properties = parent_subconfig.properties
settings = parent_subconfig.config_bag.context.get_settings()
parent_subconfig.properties = settings.getproperties(parent_subconfig)
if not self.check_force_to_metaconfig(parent_subconfig):
parent_subconfig.properties = ori_properties - {
"force_metaconfig_on_freeze"
}
else:
doption_bag.properties = ori_properties
parent_subconfig.properties = ori_properties
parent_owner = parent.get_values().getowner(
doption_bag,
parent,
parent_subconfig,
only_default=True,
)
if parent_owner != owners.default:
return doption_bag
return parent_subconfig
return None
@ -517,9 +508,6 @@ class Values:
was present
:returns: a `setting.owners.Owner` object
"""
# context = subconfig.config_bag.context
# settings = context.get_settings()
# settings.validate_properties(subconfig)
if (
"frozen" in subconfig.properties
and "force_default_on_freeze" in subconfig.properties
@ -548,11 +536,10 @@ class Values:
values = msubconfig.config_bag.context.get_values()
owner = values.getowner(
msubconfig,
parent,
only_default=only_default,
)
elif "force_metaconfig_on_freeze" in subconfig.properties:
return owners.default
owner = owners.default
return owner
def set_owner(
@ -749,8 +736,8 @@ class Values:
if index >= length:
raise IndexError(
_(
"index {index} is greater than the length {length} "
"for option {subconfig.option.impl_get_display_name(with_quote=True)}"
f"index {index} is greater than the length {length} "
f"for option {subconfig.option.impl_get_display_name(subconfig, with_quote=True)}"
)
)
current_value.pop(index)