feat: better identifiers retrieve

This commit is contained in:
egarette@silique.fr 2026-04-29 15:25:56 +02:00
parent f3a7344b58
commit 6d56e25524
7 changed files with 157 additions and 62 deletions

View file

@ -1,6 +0,0 @@
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "semver"
version = "4.1.0"
update_changelog_on_bump = true

View file

@ -508,8 +508,8 @@ def test_dyndescription_subdyn():
assert set(cfg.option('od.dod2val1.dodval1.st').property.get()) == {'validator'} assert set(cfg.option('od.dod2val1.dodval1.st').property.get()) == {'validator'}
assert set(cfg.option('od.dod2val1.dodval1.st').property.get(uncalculated=True)) == {'validator'} assert set(cfg.option('od.dod2val1.dodval1.st').property.get(uncalculated=True)) == {'validator'}
assert set(cfg.option('od.dod2.dod.st').property.get(uncalculated=True)) == {'validator'} assert set(cfg.option('od.dod2.dod.st').property.get(uncalculated=True)) == {'validator'}
with pytest.raises(AttributeOptionError): # with pytest.raises(AttributeOptionError):
cfg.option('od.dod2.dodval1.st').property.get(uncalculated=True) assert set(cfg.option('od.dod2.dodval1.st').property.get(uncalculated=True)) == {'validator'}
assert cfg.option('od.dod2val1.dod.st').property.get(uncalculated=True) == {'validator'} assert cfg.option('od.dod2val1.dod.st').property.get(uncalculated=True) == {'validator'}
# #
with pytest.raises(AttributeOptionError): with pytest.raises(AttributeOptionError):
@ -540,11 +540,13 @@ def test_dyndescription_subdyn():
assert cfg.option('od.dod2.dod').group_type() == "default" assert cfg.option('od.dod2.dod').group_type() == "default"
assert cfg.option('od.dod2val1.dodval1').group_type() == "default" assert cfg.option('od.dod2val1.dodval1').group_type() == "default"
# #
with pytest.raises(AttributeOptionError): assert cfg.option('od.dod2.dod').identifiers() == [['val1', 'val1'], ['val1', 'val2'], ['val2', 'val1'], ['val2', 'val2']]
cfg.option('od.dod2.dod').identifiers()
assert cfg.option('od.dod2.dod').identifiers(only_self=True) == ['val1', 'val2'] assert cfg.option('od.dod2.dod').identifiers(only_self=True) == ['val1', 'val2']
assert cfg.option('od.dod2val1.dodval1').identifiers() == ['val1', 'val1'] assert cfg.option('od.dod2val1.dodval1').identifiers() == ['val1', 'val1']
assert cfg.option('od.dod2val1.dodval1').identifiers(only_self=True) == ['val1', 'val2'] assert cfg.option('od.dod2val1.dodval1').identifiers(only_self=True) == ['val1', 'val2']
assert cfg.option('od.dod2val1.dodval2').identifiers() == ['val1', 'val2']
assert cfg.option('od.dod2val1.dod').identifiers() == [['val1', 'val1'], ['val1', 'val2']]
assert cfg.option('od.dod2.dodval1.st').identifiers() == [['val1', 'val1'], ['val2', 'val1']]
def test_callback_dyndescription_subdyn(): def test_callback_dyndescription_subdyn():
@ -562,6 +564,37 @@ def test_callback_dyndescription_subdyn():
assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3']} assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3']}
cfg.option('lst').value.set(["val1", "val3", None]) cfg.option('lst').value.set(["val1", "val3", None])
assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3', None]} assert parse_od_get(cfg.value.get()) == {'od.dod2val1.dodval1.st': 'val1', 'od.dod2val1.dodval3.st': 'val1', 'od.dod2val1.out': ['val1', 'val1'], 'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'lst': ['val1', 'val3', None]}
assert cfg.option('od.dod2.dod.st').identifiers() == [['val1', 'val1'], ['val1', 'val3'], ['val3', 'val1'], ['val3', 'val3']]
assert cfg.option('od.dod2.dod').identifiers(only_self=True) == ['val1', 'val3']
assert cfg.option('od.dod2val1.dod.st').identifiers() == [['val1', 'val1'], ['val1', 'val3']]
assert cfg.option('od.dod2val1.dodval1.st').identifiers() == ['val1', 'val1']
assert cfg.option('od.dod2val1.dodval1').identifiers(only_self=True) == ['val1', 'val3']
def test_callback_dyndescription_subdyn2():
lst1 = StrOption('lst1', '', ['val1', 'val2'], multi=True)
lst2 = StrOption('lst2', '', ['val3', 'val4'], multi=True)
st = StrOption('st', '', 'val1')
dod = DynOptionDescription('dod', '', [st], identifiers=Calculation(return_list, Params(ParamOption(lst1))))
out = StrOption('out', '', Calculation(return_dynval, Params(ParamDynOption(st, [None, 'val1']))), multi=True, properties=('notunique',))
dod2 = DynOptionDescription('dod2', '', [dod, out], identifiers=Calculation(return_list, Params(ParamOption(lst2))))
od = OptionDescription('od', '', [dod2])
od2 = OptionDescription('od', '', [od, lst1, lst2])
cfg = Config(od2)
cfg.property.read_write()
#
assert parse_od_get(cfg.value.get()) == {'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval2.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'od.dod2val4.dodval1.st': 'val1', 'od.dod2val4.dodval2.st': 'val1', 'od.dod2val4.out': ['val1', 'val1'], 'lst1': ['val1', 'val2'], 'lst2': ['val3', 'val4']}
#
cfg.option('lst1').value.set(["val1", "val3"])
assert parse_od_get(cfg.value.get()) == {'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'od.dod2val4.dodval1.st': 'val1', 'od.dod2val4.dodval3.st': 'val1', 'od.dod2val4.out': ['val1', 'val1'], 'lst1': ['val1', 'val3'], 'lst2': ['val3', 'val4']}
#
cfg.option('lst1').value.set(["val1", "val3", None])
assert parse_od_get(cfg.value.get()) == {'od.dod2val3.dodval1.st': 'val1', 'od.dod2val3.dodval3.st': 'val1', 'od.dod2val3.out': ['val1', 'val1'], 'od.dod2val4.dodval1.st': 'val1', 'od.dod2val4.dodval3.st': 'val1', 'od.dod2val4.out': ['val1', 'val1'], 'lst1': ['val1', 'val3', None], 'lst2': ['val3', 'val4']}
assert cfg.option('od.dod2.dod.st').identifiers() == [['val3', 'val1'], ['val3', 'val3'], ['val4', 'val1'], ['val4', 'val3']]
assert cfg.option('od.dod2.dod').identifiers(only_self=True) == ['val1', 'val3']
assert cfg.option('od.dod2val3.dod.st').identifiers() == [['val3', 'val1'], ['val3', 'val3']]
assert cfg.option('od.dod2val3.dodval1.st').identifiers() == ['val3', 'val1']
assert cfg.option('od.dod2val3.dodval1').identifiers(only_self=True) == ['val1', 'val3']
def test_callback_list_dyndescription(): def test_callback_list_dyndescription():
@ -2220,6 +2253,8 @@ def test_leadership_dyndescription_convert():
assert cfg.option('od.stval2.st1.st1').owner.isdefault() assert cfg.option('od.stval2.st1.st1').owner.isdefault()
assert cfg.option('od.stval2.st1.st1').identifiers() == ["val.2"] assert cfg.option('od.stval2.st1.st1').identifiers() == ["val.2"]
assert cfg.option('od.stval2.st1.st1').identifiers(convert=True) == ["val2"] assert cfg.option('od.stval2.st1.st1').identifiers(convert=True) == ["val2"]
assert cfg.option('od.st.st1.st1').identifiers() == [["val.1"], ["val.2"]]
assert cfg.option('od.st.st1.st1').identifiers(convert=True) == [["val1"], ["val2"]]
assert cfg.option('od.stval2').identifiers(only_self=True) == ["val.1", "val.2"] assert cfg.option('od.stval2').identifiers(only_self=True) == ["val.1", "val.2"]
assert cfg.option('od.stval2').identifiers(only_self=True, convert=True) == ["val1", "val2"] assert cfg.option('od.stval2').identifiers(only_self=True, convert=True) == ["val1", "val2"]
# #

View file

@ -1186,7 +1186,6 @@ def test_leadership_requires_leadership(config_type):
# #
cfg.option('activate').value.set(False) cfg.option('activate').value.set(False)
if config_type != 'tiramisu-api': if config_type != 'tiramisu-api':
# FIXME
with pytest.raises(PropertiesOptionError): with pytest.raises(PropertiesOptionError):
cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() cfg.option('ip_admin_eth0.ip_admin_eth0').value.get()
with pytest.raises(PropertiesOptionError): with pytest.raises(PropertiesOptionError):

View file

@ -135,11 +135,13 @@ class CommonTiramisu(TiramisuHelp):
config_bag=self._config_bag, config_bag=self._config_bag,
parent=subconfig.parent, parent=subconfig.parent,
identifiers=subconfig.identifiers, identifiers=subconfig.identifiers,
identifier=None,
true_path=subconfig.true_path, true_path=subconfig.true_path,
properties=subconfig.properties, properties=subconfig.properties,
validate_properties=False, validate_properties=False,
check_dynamic_without_identifiers=False, check_dynamic_without_identifiers=False,
) )
self._subconfig.is_self_dynamic_without_identifiers = subconfig.is_self_dynamic_without_identifiers
else: else:
self._subconfig._length = None self._subconfig._length = None
if not self._subconfig: if not self._subconfig:
@ -148,18 +150,14 @@ class CommonTiramisu(TiramisuHelp):
self._config_bag, self._config_bag,
self._path, self._path,
self._index, self._index,
validate_properties=False, validate_properties=self._validate_properties,
allow_dynoption=True, allow_dynoption=True,
) )
except AssertionError as err: except AssertionError as err:
raise ConfigError(str(err)) raise ConfigError(str(err))
def option_type(typ): def option_type(types):
if not isinstance(typ, list):
types = [typ]
else:
types = typ
def wrapper(func): def wrapper(func):
@wraps(func) @wraps(func)
@ -179,7 +177,7 @@ def option_type(typ):
return func(self, options_bag, *args[1:], **kwargs) return func(self, options_bag, *args[1:], **kwargs)
self._set_subconfig() self._set_subconfig()
if ( if (
not isinstance(typ, list) or "allow_dynoption" not in typ "allow_dynoption" not in types and not ("dynoption_or_uncalculated" in types and kwargs.get("uncalculated", False) is True)
) and self._subconfig.is_dynamic_without_identifiers: ) and self._subconfig.is_dynamic_without_identifiers:
raise AttributeOptionError(self._subconfig.path, "option-dynamic") raise AttributeOptionError(self._subconfig.path, "option-dynamic")
@ -595,43 +593,88 @@ class _TiramisuOptionOptionDescription:
) )
return ret return ret
def has_identifiers(self):
return self._subconfig.is_dynamic_without_identifiers
@option_type(["dynamic", "with_or_without_index", "allow_dynoption"]) @option_type(["dynamic", "with_or_without_index", "allow_dynoption"])
def identifiers( def identifiers(
self, self,
*,
only_self: bool = False, only_self: bool = False,
uncalculated: bool = False, uncalculated: bool = False,
convert: bool = False, convert: bool = False,
): ):
"""Get identifiers for dynamic option""" """Get identifiers for dynamic option"""
subconfig = self._subconfig subconfig = self._subconfig
if not only_self: if only_self:
if self._subconfig.is_dynamic_without_identifiers and not uncalculated: func = self._identifiers_only_self
raise AttributeOptionError(self._subconfig.path, "option-dynamic") elif not subconfig.is_dynamic_without_identifiers:
if not convert: func = self._identifiers_all
return self._subconfig.identifiers else:
identifiers = [] func = self._identifiers_all_no_identifiers
return func(subconfig, uncalculated, convert)
def _identifiers_all(self, subconfig, uncalculated, convert):
dynconfig = None dynconfig = None
_subconfig = subconfig
while not dynconfig: while not dynconfig:
if subconfig.option.impl_is_optiondescription() and subconfig.option.impl_is_dynoptiondescription(): if _subconfig.option.impl_is_optiondescription() and _subconfig.option.impl_is_dynoptiondescription():
dynconfig = subconfig dynconfig = _subconfig
subconfig = subconfig.parent else:
for identifier in self._subconfig.identifiers: _subconfig = _subconfig.parent
if identifier is None: identifiers = []
continue for identifier in _subconfig.identifiers:
identifiers.append(dynconfig.option.convert_identifier_to_path(identifier)) if convert:
identifier = dynconfig.option.convert_identifier_to_path(identifier)
identifiers.append(identifier)
return identifiers return identifiers
def _identifiers_all_no_identifiers(self, subconfig, uncalculated, convert):
"""dyn{{ identifier }}.var{{ identifier }}.var
=> dyn["val1", "val2"]
=> var["val3", "val4"]
returns [["val1", "val3"], ["val1", "val4"], ["val2", "val3"], ["val2", "val4"]]
"""
identifiers = []
while True:
if subconfig.option.impl_is_optiondescription() and subconfig.option.impl_is_dynoptiondescription():
if not subconfig.is_self_dynamic_without_identifiers:
new_identifiers = [subconfig.identifiers[-1]]
else:
new_identifiers = subconfig.option.get_identifiers(subconfig.parent, uncalculated=uncalculated, convert=convert)
if isinstance(new_identifiers, Calculation):
if identifiers:
identifiers = [[new_identifiers] + old_identifiers for old_identifiers in identifiers]
else:
identifiers = [new_identifiers]
elif identifiers:
identifiers = [[identifier] + old_identifiers for identifier in new_identifiers for old_identifiers in identifiers]
else:
identifiers = [[identifier] for identifier in new_identifiers]
subconfig = subconfig.parent
if subconfig is None:
break
return identifiers
def _identifiers_only_self(self, subconfig, uncalculated, convert):
if ( if (
not self._subconfig.option.impl_is_optiondescription() not subconfig.option.impl_is_optiondescription()
or not self._subconfig.option.impl_is_dynoptiondescription() or not subconfig.option.impl_is_dynoptiondescription()
): ):
raise ConfigError( raise ConfigError(
_( _(
"the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True" "the option {0} is not a dynamic option, cannot get identifiers with only_self parameter to True"
).format(self._subconfig.path), ).format(subconfig.path),
subconfig=subconfig, subconfig=subconfig,
) )
return self._subconfig.option.get_identifiers( dynconfig = None
self._subconfig.parent, _subconfig = subconfig
while not dynconfig:
if _subconfig.option.impl_is_optiondescription() and _subconfig.option.impl_is_dynoptiondescription():
dynconfig = _subconfig
_subconfig = _subconfig.parent
return dynconfig.option.get_identifiers(
dynconfig.parent,
uncalculated=uncalculated, uncalculated=uncalculated,
convert=convert, convert=convert,
) )
@ -1010,7 +1053,7 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
_validate_properties = True _validate_properties = True
@option_type(["option", "symlink", "with_index", "optiondescription"]) @option_type(["option", "symlink", "with_index", "optiondescription", "dynoption_or_uncalculated"])
def get( def get(
self, self,
*, *,
@ -1074,7 +1117,7 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
values.reset(self._subconfig) values.reset(self._subconfig)
@option_type( @option_type(
["option", "with_or_without_index", "symlink", "dont_validate_property"] ["option", "with_or_without_index", "symlink", "dont_validate_property", "dynoption_or_uncalculated"]
) )
def default( def default(
self, self,
@ -1113,7 +1156,7 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
return False return False
return True return True
@option_type(["choice", "with_index", "allow_dynoption"]) @option_type(["choice", "with_index_or_uncalculated", "allow_dynoption"])
def list( def list(
self, self,
*, *,
@ -1127,7 +1170,7 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
uncalculated, uncalculated,
) )
@option_type("leader") @option_type(["leader"])
def pop( def pop(
self, self,
index: int, index: int,
@ -1350,7 +1393,7 @@ class TiramisuOption(
remotable=remotable, remotable=remotable,
) )
@option_type("optiondescription") @option_type(["optiondescription"])
def dict( def dict(
self, self,
clearable: str = "all", clearable: str = "all",
@ -1363,7 +1406,7 @@ class TiramisuOption(
self._load_dict(clearable, remotable) self._load_dict(clearable, remotable)
return self._tiramisu_dict.todict(form) return self._tiramisu_dict.todict(form)
@option_type("optiondescription") @option_type(["optiondescription"])
def updates( def updates(
self, self,
body: List, body: List,
@ -1445,17 +1488,20 @@ class TiramisuContextValue(TiramisuConfig, _TiramisuODGet):
only_mandatory=True, only_mandatory=True,
): ):
if id(subconfig.config_bag) != id(config_bag): if id(subconfig.config_bag) != id(config_bag):
old_is_dynamic_without_identifiers = subconfig.is_self_dynamic_without_identifiers
subconfig = subconfig.__class__(option=subconfig.option, subconfig = subconfig.__class__(option=subconfig.option,
index=subconfig.index, index=subconfig.index,
path=subconfig.path, path=subconfig.path,
config_bag=config_bag, config_bag=config_bag,
parent=subconfig.parent, parent=subconfig.parent,
identifiers=subconfig.identifiers, identifiers=subconfig.identifiers,
identifier=None,
true_path=subconfig.true_path, true_path=subconfig.true_path,
properties=subconfig.properties, properties=subconfig.properties,
validate_properties=False, validate_properties=False,
check_dynamic_without_identifiers=False, check_dynamic_without_identifiers=False,
) )
subconfig.is_self_dynamic_without_identifiers = old_is_dynamic_without_identifiers
else: else:
subconfig._length = None subconfig._length = None
options.append( options.append(

View file

@ -691,6 +691,7 @@ def manager_callback(
subconfigs_is_a_list = False subconfigs_is_a_list = False
for name in paths: for name in paths:
new_parents = [] new_parents = []
identifier = undefined
for parent in parents: for parent in parents:
doption = parent.option.get_child( doption = parent.option.get_child(
name, name,
@ -699,11 +700,12 @@ def manager_callback(
allow_dynoption=True, allow_dynoption=True,
) )
if doption.impl_is_dynoptiondescription(): if doption.impl_is_dynoptiondescription():
if identifier is undefined:
if not identifiers: if not identifiers:
identifier = None identifier = None
else: else:
identifier = identifiers.pop(0) identifier = identifiers.pop(0)
if not identifier: if identifier is None:
subconfigs_is_a_list = True subconfigs_is_a_list = True
new_parents.extend( new_parents.extend(
parent.dyn_to_subconfig( parent.dyn_to_subconfig(
@ -879,17 +881,20 @@ def carry_out_calculation(
kwargs = {} kwargs = {}
config_bag = config_bag.copy() config_bag = config_bag.copy()
config_bag.set_permissive() config_bag.set_permissive()
old_is_dynamic_without_identifiers = subconfig.is_dynamic_without_identifiers
subconfig = subconfig.__class__(option=subconfig.option, subconfig = subconfig.__class__(option=subconfig.option,
index=subconfig.index, index=subconfig.index,
path=subconfig.path, path=subconfig.path,
config_bag=config_bag, config_bag=config_bag,
parent=subconfig.parent, parent=subconfig.parent,
identifiers=subconfig.identifiers, identifiers=subconfig.identifiers,
identifier=None,
true_path=subconfig.true_path, true_path=subconfig.true_path,
properties=subconfig.properties, properties=subconfig.properties,
validate_properties=False, validate_properties=False,
check_dynamic_without_identifiers=False, check_dynamic_without_identifiers=False,
) )
subconfig.is_dynamic_without_identifiers = old_is_dynamic_without_identifiers
if callback_params: if callback_params:
for key, param in chain( for key, param in chain(
fake_items(callback_params.args), callback_params.kwargs.items() fake_items(callback_params.args), callback_params.kwargs.items()

View file

@ -275,6 +275,7 @@ class SubConfig:
"transitive_properties", "transitive_properties",
"is_dynamic", "is_dynamic",
"is_dynamic_without_identifiers", "is_dynamic_without_identifiers",
"is_self_dynamic_without_identifiers",
"identifiers", "identifiers",
"_length", "_length",
) )
@ -287,6 +288,7 @@ class SubConfig:
config_bag: ConfigBag, config_bag: ConfigBag,
parent: Optional["SubConfig"], parent: Optional["SubConfig"],
identifiers: Optional[list[str]], identifiers: Optional[list[str]],
identifier: Optional[str],
*, *,
true_path: Optional[str] = None, true_path: Optional[str] = None,
# for python 3.9 properties: Union[list[str], undefined] = undefined, # for python 3.9 properties: Union[list[str], undefined] = undefined,
@ -308,6 +310,7 @@ class SubConfig:
) )
self.apply_requires = not is_follower or index is not None self.apply_requires = not is_follower or index is not None
self.true_path = true_path self.true_path = true_path
self.is_self_dynamic_without_identifiers = identifier is None
if self.option.impl_is_dynoptiondescription(): if self.option.impl_is_dynoptiondescription():
self.is_dynamic = True self.is_dynamic = True
self.is_dynamic_without_identifiers = identifiers is None or ( self.is_dynamic_without_identifiers = identifiers is None or (
@ -321,7 +324,8 @@ class SubConfig:
and self.is_dynamic_without_identifiers and self.is_dynamic_without_identifiers
!= parent.is_dynamic_without_identifiers != parent.is_dynamic_without_identifiers
): ):
raise AttributeOptionError(true_path, "option-dynamic") self.is_dynamic_without_identifiers = True
# raise AttributeOptionError(true_path, "option-dynamic")
elif parent: elif parent:
self.is_dynamic = parent.is_dynamic self.is_dynamic = parent.is_dynamic
self.is_dynamic_without_identifiers = parent.is_dynamic_without_identifiers self.is_dynamic_without_identifiers = parent.is_dynamic_without_identifiers
@ -339,8 +343,7 @@ class SubConfig:
self.config_bag.context.get_settings().validate_properties(self) self.config_bag.context.get_settings().validate_properties(self)
self._properties = undefined self._properties = undefined
self.config_bag.context.get_settings().validate_properties(self) self.config_bag.context.get_settings().validate_properties(self)
if self.apply_requires and self.option.impl_is_optiondescription(): if validate_properties and self.apply_requires and self.option.impl_is_optiondescription() and self.path and self.properties is not None:
if self.path and self.properties is not None:
settings = config_bag.context.get_settings() settings = config_bag.context.get_settings()
self.transitive_properties = settings.calc_transitive_properties( self.transitive_properties = settings.calc_transitive_properties(
self, self,
@ -515,6 +518,7 @@ class SubConfig:
config_bag, config_bag,
self, self,
identifiers, identifiers,
identifier,
properties=properties, properties=properties,
validate_properties=validate_properties, validate_properties=validate_properties,
true_path=true_path, true_path=true_path,
@ -560,8 +564,10 @@ class SubConfig:
cconfig_bag, cconfig_bag,
self, self,
self.identifiers, self.identifiers,
None,
validate_properties=False, validate_properties=False,
) )
subconfig.is_self_dynamic_without_identifiers = self.is_self_dynamic_without_identifiers
#FIXME #FIXME
#self._length = len(cconfig_bag.context.get_value(subconfig)) #self._length = len(cconfig_bag.context.get_value(subconfig))
length = len(cconfig_bag.context.get_value(subconfig)) length = len(cconfig_bag.context.get_value(subconfig))
@ -664,16 +670,19 @@ class SubConfig:
def change_context(self, context) -> "SubConfig": def change_context(self, context) -> "SubConfig":
config_bag = self.config_bag.copy() config_bag = self.config_bag.copy()
config_bag.context = context config_bag.context = context
return SubConfig( subconfig = SubConfig(
self.option, self.option,
self.index, self.index,
self.path, self.path,
config_bag, config_bag,
self.parent, self.parent,
self.identifiers, self.identifiers,
None,
true_path=self.true_path, true_path=self.true_path,
validate_properties=False, validate_properties=False,
) )
subconfig.is_self_dynamic_without_identifiers = self.is_self_dynamic_without_identifiers
return subconfig
class _Config(CCache): class _Config(CCache):
@ -761,6 +770,7 @@ class _Config(CCache):
config_bag, config_bag,
None, None,
None, None,
None,
) )
def get_sub_config( def get_sub_config(

View file

@ -227,6 +227,12 @@ class Option(BaseOption):
default = getattr(self, "_default", undefined) default = getattr(self, "_default", undefined)
if default is undefined: if default is undefined:
if is_multi: if is_multi:
if self.impl_is_follower():
if submulti:
default = getattr(self, "_default_multi", [])
else:
default = getattr(self, "_default_multi", None)
else:
default = [] default = []
else: else:
default = None default = None