fix: better permissive support

This commit is contained in:
egarette@silique.fr 2024-10-22 11:04:57 +02:00
parent 7761758096
commit 7ae1b48f4a
3 changed files with 179 additions and 123 deletions

View file

@ -21,7 +21,7 @@ from functools import wraps
from copy import deepcopy from copy import deepcopy
from .error import ConfigError, LeadershipError, ValueErrorWarning from .error import ConfigError, LeadershipError, ValueErrorWarning, PropertiesOptionError
from .i18n import _ from .i18n import _
from .setting import ConfigBag, owners, groups, undefined, \ from .setting import ConfigBag, owners, groups, undefined, \
FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, \ FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, \
@ -92,6 +92,7 @@ class CommonTiramisu(TiramisuHelp):
_allow_dynoption = False _allow_dynoption = False
def _set_subconfig(self) -> None: def _set_subconfig(self) -> None:
if not self._subconfig:
self._subconfig = self._config_bag.context.get_sub_config(self._config_bag, self._subconfig = self._config_bag.context.get_sub_config(self._config_bag,
self._path, self._path,
self._index, self._index,
@ -119,6 +120,7 @@ def option_type(typ):
)] )]
kwargs['is_group'] = True kwargs['is_group'] = True
return func(self, options_bag, *args[1:], **kwargs) return func(self, options_bag, *args[1:], **kwargs)
self._set_subconfig()
option = self._subconfig.option option = self._subconfig.option
error_type = None error_type = None
if 'dynamic' in types: if 'dynamic' in types:
@ -194,6 +196,7 @@ class CommonTiramisuOption(CommonTiramisu):
self._path = path self._path = path
self._index = index self._index = index
self._config_bag = config_bag self._config_bag = config_bag
self._subconfig = None
self._set_subconfig() self._set_subconfig()
@ -292,6 +295,11 @@ class _TiramisuOptionOptionDescription:
return 'optiondescription' return 'optiondescription'
return option.get_type() return option.get_type()
@option_type(['option', 'symlink', 'with_or_without_index'])
def extra(self, extra):
"""Get de option extra"""
return self._subconfig.option.impl_get_extra(extra)
@option_type(['option', 'optiondescription', 'symlink', 'with_or_without_index']) @option_type(['option', 'optiondescription', 'symlink', 'with_or_without_index'])
def isdynamic(self, def isdynamic(self,
*, *,
@ -342,17 +350,17 @@ class _TiramisuOptionOptionDescription:
return ret return ret
@option_type(['dynamic', 'with_or_without_index']) @option_type(['dynamic', 'with_or_without_index'])
def suffixes(self, def identifiers(self,
only_self: bool=False, only_self: bool=False,
uncalculated: bool=False, uncalculated: bool=False,
): ):
"""Get suffixes for dynamic option""" """Get identifiers for dynamic option"""
if not only_self: if not only_self:
return self._subconfig.suffixes return self._subconfig.identifiers
if not self._subconfig.option.impl_is_optiondescription() or \ if not self._subconfig.option.impl_is_optiondescription() or \
not self._subconfig.option.impl_is_dynoptiondescription(): not self._subconfig.option.impl_is_dynoptiondescription():
raise ConfigError(_(f'the option {self._subconfig.path} is not a dynamic option, cannot get suffixes with only_self parameter to True')) raise ConfigError(_(f'the option {self._subconfig.path} is not a dynamic option, cannot get identifiers with only_self parameter to True'))
return self._subconfig.option.get_suffixes(self._subconfig.parent, return self._subconfig.option.get_identifiers(self._subconfig.parent,
uncalculated=uncalculated, uncalculated=uncalculated,
) )
@ -571,6 +579,7 @@ class TiramisuOptionPermissive(CommonTiramisuOption):
class TiramisuOptionInformation(CommonTiramisuOption): class TiramisuOptionInformation(CommonTiramisuOption):
"""Manage option's informations""" """Manage option's informations"""
_validate_properties = False _validate_properties = False
_allow_dynoption = True
@option_type(['option', 'optiondescription', 'with_or_without_index', 'symlink']) @option_type(['option', 'optiondescription', 'with_or_without_index', 'symlink'])
def get(self, def get(self,
@ -670,8 +679,8 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
option = self._subconfig.option option = self._subconfig.option
if not isinstance(value, Calculation) and option.impl_is_leader() and \ if not isinstance(value, Calculation) and option.impl_is_leader() and \
len(value) < self._subconfig.parent.get_length_leadership(): len(value) < self._subconfig.parent.get_length_leadership():
raise LeadershipError(_('cannot reduce length of the leader "{}"' raise LeadershipError(_('cannot reduce length of the leader {}'
'').format(option.impl_get_display_name(self._subconfig))) '').format(option.impl_get_display_name(self._subconfig, with_quote=True)))
values = self._config_bag.context.get_values() values = self._config_bag.context.get_values()
return values.set_value(self._subconfig, return values.set_value(self._subconfig,
value value
@ -753,6 +762,32 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet):
"""Length for a leadership""" """Length for a leadership"""
return self._subconfig.parent.get_length_leadership() return self._subconfig.parent.get_length_leadership()
def mandatory(self):
"""Return path of options with mandatory property without any value"""
subconfig = self._subconfig
if subconfig.option.impl_is_optiondescription():
ori_config_bag = self._subconfig.config_bag
config_bag = ori_config_bag.copy()
config_bag.properties -= {'mandatory', 'empty', 'warnings'}
config_bag.set_permissive()
self._subconfig.config_bag = config_bag
options = []
for subconfig in self._config_bag.context.walk(self._subconfig,
only_mandatory=True,
):
options.append(TiramisuOption(subconfig.path,
subconfig.index,
ori_config_bag,
subconfig=subconfig,
))
self._subconfig.config_bag = ori_config_bag
return options
try:
self._config_bag.context.walk_valid_value(self._subconfig, only_mandatory=True)
except PropertiesOptionError as err:
return err.proptype == ['mandatory'] or err.proptype == ['empty']
return False
def _registers(_registers: Dict[str, type], def _registers(_registers: Dict[str, type],
prefix: str, prefix: str,
@ -808,9 +843,6 @@ class TiramisuOption(CommonTiramisu,
self._index = index self._index = index
self._config_bag = config_bag self._config_bag = config_bag
self._allow_dynoption = allow_dynoption self._allow_dynoption = allow_dynoption
if subconfig is None:
self._set_subconfig()
else:
self._subconfig = subconfig self._subconfig = subconfig
if not self._registers: if not self._registers:
_registers(self._registers, 'TiramisuOption') _registers(self._registers, 'TiramisuOption')
@ -831,6 +863,7 @@ class TiramisuOption(CommonTiramisu,
raise ConfigError(_(f'please specify a valid sub function ({self.__class__.__name__}.{subfunc}) for {self._path}')) raise ConfigError(_(f'please specify a valid sub function ({self.__class__.__name__}.{subfunc}) for {self._path}'))
# #
def __iter__(self): def __iter__(self):
self._set_subconfig()
for sub_subconfig in self._subconfig.get_children(True): for sub_subconfig in self._subconfig.get_children(True):
yield TiramisuOption(sub_subconfig.path, yield TiramisuOption(sub_subconfig.path,
sub_subconfig.index, sub_subconfig.index,
@ -841,6 +874,7 @@ class TiramisuOption(CommonTiramisu,
@option_type('optiondescription') @option_type('optiondescription')
def group_type(self): def group_type(self):
"""Get type for an optiondescription (only for optiondescription)""" """Get type for an optiondescription (only for optiondescription)"""
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')
@ -850,6 +884,7 @@ class TiramisuOption(CommonTiramisu,
uncalculated: bool=False, uncalculated: bool=False,
): ):
"""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()
return self._list(self._subconfig, return self._list(self._subconfig,
validate_properties, validate_properties,
uncalculated=uncalculated, uncalculated=uncalculated,
@ -1335,12 +1370,15 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk):
return 'optiondescription' return 'optiondescription'
def list(self, def list(self,
*,
validate_properties: bool=True, validate_properties: bool=True,
uncalculated: bool=False,
): ):
"""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)
return self._list(root, return self._list(root,
validate_properties, validate_properties,
uncalculated=uncalculated,
) )
def _load_dict(self, def _load_dict(self,

View file

@ -115,12 +115,12 @@ class ParamOption(Param):
class ParamDynOption(ParamOption): class ParamDynOption(ParamOption):
__slots__ = ('suffixes', __slots__ = ('identifiers',
'optional', 'optional',
) )
def __init__(self, def __init__(self,
option: 'Option', option: 'Option',
suffixes: list[str], identifiers: list[str],
notraisepropertyerror: bool=False, notraisepropertyerror: bool=False,
raisepropertyerror: bool=False, raisepropertyerror: bool=False,
optional: bool=False, optional: bool=False,
@ -129,7 +129,11 @@ class ParamDynOption(ParamOption):
notraisepropertyerror, notraisepropertyerror,
raisepropertyerror, raisepropertyerror,
) )
self.suffixes = suffixes if not isinstance(identifiers, list):
raise Exception(f'identifiers in ParamDynOption must be a list, not {identifiers}')
if not isinstance(optional, bool):
raise Exception(f'optional in ParamDynOption must be a boolean, not {optional}')
self.identifiers = identifiers
self.optional = optional self.optional = optional
@ -212,12 +216,12 @@ class ParamIndex(Param):
__slots__ = tuple() __slots__ = tuple()
class ParamSuffix(Param): class ParamIdentifier(Param):
__slots__ = ('suffix_index',) __slots__ = ('identifier_index',)
def __init__(self, def __init__(self,
suffix_index: int=-1, identifier_index: int=-1,
) -> None: ) -> None:
self.suffix_index = suffix_index self.identifier_index = identifier_index
class Calculation: class Calculation:
@ -345,7 +349,7 @@ def manager_callback(callback: Callable,
properties=undefined, properties=undefined,
): ):
option = subconfig.option option = subconfig.option
if option.impl_is_follower() and apply_index is False: if option.impl_is_follower() and (subconfig.index is None or apply_index is False):
value = [] value = []
for idx in range(subconfig.parent.get_length_leadership()): for idx in range(subconfig.parent.get_length_leadership()):
subconfig = get_option_bag(config_bag, subconfig = get_option_bag(config_bag,
@ -374,10 +378,11 @@ def manager_callback(callback: Callable,
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation # raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if isinstance(param, ParamSelfOption) or param.notraisepropertyerror or param.raisepropertyerror: if isinstance(param, ParamSelfOption) or param.notraisepropertyerror or param.raisepropertyerror:
raise err from err raise err from err
raise ConfigError(_('unable to carry out a calculation for "{}", {}').format(display_name, err)) from err display_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_('unable to carry out a calculation for {}, {}').format(display_name, err)) from err
except ValueError as err: except ValueError as err:
display_name = subconfig.option.impl_get_display_name(subconfig) display_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True)
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(display_name, err)) from err raise ValueError(_('the option {0} is used in a calculation but is invalid ({1})').format(display_name, err)) from err
except AttributeError as err: except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional: if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror # cannot acces, simulate a propertyerror
@ -385,8 +390,8 @@ def manager_callback(callback: Callable,
['configerror'], ['configerror'],
config_bag.context.get_settings(), config_bag.context.get_settings(),
) )
display_name = subconfig.option.impl_get_display_name(subconfig) display_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err raise ConfigError(_(f'unable to get value for calculating {display_name}, {err}')) from err
return value return value
def get_option_bag(config_bag, def get_option_bag(config_bag,
@ -398,16 +403,13 @@ def manager_callback(callback: Callable,
properties=undefined, properties=undefined,
): ):
# don't validate if option is option that we tried to validate # don't validate if option is option that we tried to validate
config_bag = config_bag.copy()
if for_settings: if for_settings:
config_bag.properties = config_bag.properties - {'warnings'} config_bag.properties = config_bag.properties - {'warnings'}
config_bag.set_permissive()
if not for_settings: if not for_settings:
config_bag.properties -= {'warnings'} config_bag.properties -= {'warnings'}
if self_calc: if self_calc:
config_bag.unrestraint() config_bag.unrestraint()
config_bag.remove_validation() config_bag.remove_validation()
# root = config_bag.context.get_root(config_bag)
try: try:
subsubconfig = config_bag.context.get_sub_config(config_bag, subsubconfig = config_bag.context.get_sub_config(config_bag,
opt.impl_getpath(), opt.impl_getpath(),
@ -419,10 +421,11 @@ def manager_callback(callback: Callable,
# raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation # raise PropertiesOptionError (which is catched) because must not add value None in carry_out_calculation
if param.notraisepropertyerror or param.raisepropertyerror: if param.notraisepropertyerror or param.raisepropertyerror:
raise err from err raise err from err
display_name = option.impl_get_display_name(subconfig) display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_('unable to carry out a calculation for "{}", {}').format(display_name, err)) from err raise ConfigError(_('unable to carry out a calculation for {}, {}').format(display_name, err)) from err
except ValueError as err: except ValueError as err:
raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(option.impl_get_display_name(subconfig), err)) from err display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ValueError(_('the option {0} is used in a calculation but is invalid ({1})').format(display_name, err)) from err
except AttributeError as err: except AttributeError as err:
if isinstance(param, ParamDynOption) and param.optional: if isinstance(param, ParamDynOption) and param.optional:
# cannot acces, simulate a propertyerror # cannot acces, simulate a propertyerror
@ -430,8 +433,8 @@ def manager_callback(callback: Callable,
['configerror'], ['configerror'],
config_bag.context.get_settings(), config_bag.context.get_settings(),
) )
display_name = option.impl_get_display_name(subconfig) display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err raise ConfigError(_(f'unable to get value for calculating {display_name}, {err}')) from err
return subsubconfig return subsubconfig
if isinstance(param, ParamValue): if isinstance(param, ParamValue):
@ -447,7 +450,9 @@ def manager_callback(callback: Callable,
true_path=subconfig.path, true_path=subconfig.path,
) )
if isinstance(isubconfig, list): if isinstance(isubconfig, list):
raise ConfigError(f'cannot find information for "{option.impl_get_display_name(subconfig)}", "{search_option.impl_get_display_name(None)}" is a dynamic option') display_name = option.impl_get_display_name(subconfig, with_quote=True)
search_name = search_option.impl_get_display_name(None, with_quote=True)
raise ConfigError(f'cannot find information for {display_name}, {search_name} is a dynamic option')
else: else:
isubconfig = get_option_bag(config_bag, isubconfig = get_option_bag(config_bag,
param.option, param.option,
@ -464,17 +469,17 @@ def manager_callback(callback: Callable,
param.default_value, param.default_value,
) )
except ValueError as err: except ValueError as err:
display_name = option.impl_get_display_name(subconfig) display_name = option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err raise ConfigError(_(f'unable to get value for calculating {display_name}, {err}')) from err
if isinstance(param, ParamIndex): if isinstance(param, ParamIndex):
return index return index
if isinstance(param, ParamSuffix): if isinstance(param, ParamIdentifier):
if not option.issubdyn() and (not option.impl_is_optiondescription() or not option.impl_is_dynoptiondescription()): if not option.issubdyn() and (not option.impl_is_optiondescription() or not option.impl_is_dynoptiondescription()):
display_name = subconfig.option.impl_get_display_name(subconfig) display_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True)
raise ConfigError(_(f'option "{display_name}" is not a dynoptiondescription or in a dynoptiondescription')) raise ConfigError(_(f'option {display_name} is not a dynoptiondescription or in a dynoptiondescription'))
return subconfig.suffixes[param.suffix_index] return subconfig.identifiers[param.identifier_index]
if isinstance(param, ParamSelfOption): if isinstance(param, ParamSelfOption):
value = calc_self(param, value = calc_self(param,
@ -490,7 +495,6 @@ def manager_callback(callback: Callable,
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
callbk_option = param.option callbk_option = param.option
config_bag = subconfig.config_bag
if index is not None and callbk_option.impl_get_leadership() and \ if index is not None and callbk_option.impl_get_leadership() and \
callbk_option.impl_get_leadership().in_same_leadership(option): callbk_option.impl_get_leadership().in_same_leadership(option):
if not callbk_option.impl_is_follower(): if not callbk_option.impl_is_follower():
@ -506,8 +510,7 @@ def manager_callback(callback: Callable,
with_index = False with_index = False
if callbk_option.issubdyn(): if callbk_option.issubdyn():
if isinstance(param, ParamDynOption): if isinstance(param, ParamDynOption):
#callbk_option = callbk_option.to_sub_dyoption(param.suffixes) identifiers = param.identifiers.copy()
suffixes = param.suffixes.copy()
paths = callbk_option.impl_getpath().split('.') paths = callbk_option.impl_getpath().split('.')
parents = [config_bag.context.get_root(config_bag)] parents = [config_bag.context.get_root(config_bag)]
subconfigs_is_a_list = False subconfigs_is_a_list = False
@ -520,9 +523,18 @@ def manager_callback(callback: Callable,
allow_dynoption=True, allow_dynoption=True,
) )
if doption.impl_is_dynoptiondescription(): if doption.impl_is_dynoptiondescription():
if suffixes: if not identifiers:
suffix = suffixes.pop(0) identifier = None
name = doption.impl_getname(suffix) else:
identifier = identifiers.pop(0)
if not identifier:
subconfigs_is_a_list = True
new_parents.extend(parent.dyn_to_subconfig(doption,
True,
)
)
else:
name = doption.impl_getname(identifier)
try: try:
doption = parent.option.get_child(name, doption = parent.option.get_child(name,
config_bag, config_bag,
@ -534,14 +546,8 @@ def manager_callback(callback: Callable,
None, None,
True, True,
name=name, name=name,
suffix=suffix, identifier=identifier,
)) ))
else:
subconfigs_is_a_list = True
new_parents.extend(parent.dyn_to_subconfig(doption,
True,
)
)
else: else:
new_parents.append(parent.get_child(doption, new_parents.append(parent.get_child(doption,
None, None,
@ -625,11 +631,13 @@ def carry_out_calculation(subconfig: 'SubConfig',
Values could have multiple values only when key is ''.""" Values could have multiple values only when key is ''."""
option = subconfig.option option = subconfig.option
if not option.impl_is_optiondescription() and option.impl_is_follower() and index is None: if not option.impl_is_optiondescription() and option.impl_is_follower() and index is None:
raise ConfigError(f'the follower "{option.impl_get_display_name(subconfig)}" must have index in carry_out_calculation!') raise ConfigError(f'the follower {option.impl_get_display_name(subconfig, with_quote=True)} must have index in carry_out_calculation!')
def fake_items(iterator): def fake_items(iterator):
return ((None, i) for i in iterator) return ((None, i) for i in iterator)
args = [] args = []
kwargs = {} kwargs = {}
config_bag = config_bag.copy()
config_bag.set_permissive()
if callback_params: if callback_params:
for key, param in chain(fake_items(callback_params.args), callback_params.kwargs.items()): for key, param in chain(fake_items(callback_params.args), callback_params.kwargs.items()):
try: try:
@ -667,18 +675,18 @@ def carry_out_calculation(subconfig: 'SubConfig',
if args or kwargs: if args or kwargs:
raise LeadershipError(_('the "{}" function with positional arguments "{}" ' raise LeadershipError(_('the "{}" function with positional arguments "{}" '
'and keyword arguments "{}" must not return ' 'and keyword arguments "{}" must not return '
'a list ("{}") for the follower option "{}"' 'a list ("{}") for the follower option {}'
'').format(callback.__name__, '').format(callback.__name__,
args, args,
kwargs, kwargs,
ret, ret,
option.impl_get_display_name(subconfig))) option.impl_get_display_name(subconfig, with_quote=True)))
else: else:
raise LeadershipError(_('the "{}" function must not return a list ("{}") ' raise LeadershipError(_('the "{}" function must not return a list ("{}") '
'for the follower option "{}"' 'for the follower option {}'
'').format(callback.__name__, '').format(callback.__name__,
ret, ret,
option.impl_get_display_name(subconfig))) option.impl_get_display_name(subconfig, with_quote=True)))
return ret return ret
@ -702,22 +710,22 @@ def calculate(subconfig,
if allow_value_error: if allow_value_error:
if force_value_warning: if force_value_warning:
raise ValueWarning(str(err)) raise ValueWarning(str(err))
raise err raise err from err
error = err error = err
except ConfigError as err:
raise err from err
except Exception as err: except Exception as err:
import traceback
traceback.print_exc()
error = err error = err
if args or kwargs: if args or kwargs:
msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" ' msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" '
'for option "{2}"').format(str(error), 'for option {2}').format(str(error),
callback.__name__, callback.__name__,
subconfig.option.impl_get_display_name(subconfig), subconfig.option.impl_get_display_name(subconfig, with_quote=True),
args, args,
kwargs) kwargs)
else: else:
msg = _('unexpected error "{0}" in function "{1}" for option "{2}"' msg = _('unexpected error "{0}" in function "{1}" for option {2}'
'').format(str(error), '').format(str(error),
callback.__name__, callback.__name__,
subconfig.option.impl_get_display_name(subconfig)) subconfig.option.impl_get_display_name(subconfig, with_quote=True))
raise ConfigError(msg) from error raise ConfigError(msg) from error

View file

@ -182,10 +182,11 @@ class SubConfig:
'index', 'index',
'path', 'path',
'true_path', 'true_path',
'properties', '_properties',
'apply_requires',
'transitive_properties', 'transitive_properties',
'is_dynamic', 'is_dynamic',
'suffixes', 'identifiers',
'_length', '_length',
) )
def __init__(self, def __init__(self,
@ -194,14 +195,14 @@ class SubConfig:
path: str, path: str,
config_bag: ConfigBag, config_bag: ConfigBag,
parent: Optional['SubConfig'], parent: Optional['SubConfig'],
suffixes: Optional[list[str]], identifiers: Optional[list[str]],
*, *,
true_path: Optional[str]=None, true_path: Optional[str]=None,
properties: Union[list[str], undefined]=undefined, properties: Union[list[str], undefined]=undefined,
validate_properties: bool=True, validate_properties: bool=True,
) -> None: ) -> None:
self.index = index self.index = index
self.suffixes = suffixes self.identifiers = identifiers
self.option = option self.option = option
self.config_bag = config_bag self.config_bag = config_bag
self.parent = parent self.parent = parent
@ -210,38 +211,47 @@ class SubConfig:
if true_path is None: if true_path is None:
true_path = path true_path = path
is_follower = not option.impl_is_optiondescription() and option.impl_is_follower() is_follower = not option.impl_is_optiondescription() and option.impl_is_follower()
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
settings = config_bag.context.get_settings()
if parent and parent.is_dynamic or self.option.impl_is_dynoptiondescription(): if parent and parent.is_dynamic or self.option.impl_is_dynoptiondescription():
self.is_dynamic = True self.is_dynamic = True
else: else:
self.is_dynamic = False self.is_dynamic = False
if properties is undefined: self._properties = properties
if path is None:
self.properties = frozenset()
else:
self.properties = frozenset()
if validate_properties: if validate_properties:
self.properties = settings.getproperties(self, if self.path and self._properties is undefined:
settings = config_bag.context.get_settings()
self._properties = settings.getproperties(self,
apply_requires=False, apply_requires=False,
) )
self.config_bag.context.get_settings().validate_properties(self) self.config_bag.context.get_settings().validate_properties(self)
self.properties = settings.getproperties(self, self._properties = undefined
apply_requires=apply_requires,
)
else:
self.properties = properties
if validate_properties:
self.config_bag.context.get_settings().validate_properties(self) self.config_bag.context.get_settings().validate_properties(self)
if apply_requires and self.option.impl_is_optiondescription(): if self.apply_requires and self.option.impl_is_optiondescription():
if self.path and self.properties is not None: if self.path and self.properties is not None:
settings = config_bag.context.get_settings()
self.transitive_properties = settings.calc_transitive_properties(self, self.transitive_properties = settings.calc_transitive_properties(self,
self.properties, self.properties,
) )
else: else:
self.transitive_properties = frozenset() self.transitive_properties = frozenset()
@property
def properties(self):
if self._properties is undefined:
if self.path is None:
self._properties = frozenset()
else:
settings = self.config_bag.context.get_settings()
self._properties = frozenset()
self._properties = settings.getproperties(self,
apply_requires=self.apply_requires,
)
return self._properties
@properties.setter
def properties(self, properties):
self._properties = properties
def __repr__(self): def __repr__(self):
return f'<SubConfig path={self.path}, index={self.index}>' return f'<SubConfig path={self.path}, index={self.index}>'
@ -253,9 +263,9 @@ class SubConfig:
true_path: Optional[str]=None, true_path: Optional[str]=None,
) -> List['SubConfig']: ) -> List['SubConfig']:
config_bag = self.config_bag config_bag = self.config_bag
for suffix in child.get_suffixes(self): for identifier in child.get_identifiers(self):
try: try:
name = child.impl_getname(suffix) name = child.impl_getname(identifier)
if not validate_properties: if not validate_properties:
properties = None properties = None
else: else:
@ -263,7 +273,7 @@ class SubConfig:
yield self.get_child(child, yield self.get_child(child,
None, None,
validate_properties, validate_properties,
suffix=suffix, identifier=identifier,
name=name, name=name,
properties=properties, properties=properties,
true_path=true_path, true_path=true_path,
@ -323,7 +333,7 @@ class SubConfig:
*, *,
properties=undefined, properties=undefined,
allow_dynoption: bool=False, allow_dynoption: bool=False,
suffix: Optional[str]=None, identifier: Optional[str]=None,
name: Optional[str]=None, name: Optional[str]=None,
check_index: bool=True, check_index: bool=True,
config_bag: ConfigBag=None, config_bag: ConfigBag=None,
@ -338,19 +348,19 @@ class SubConfig:
path = self.get_path(name, path = self.get_path(name,
option, option,
) )
if suffix is None: if identifier is None:
suffixes = self.suffixes identifiers = self.identifiers
else: else:
if self.suffixes: if self.identifiers:
suffixes = self.suffixes + [suffix] identifiers = self.identifiers + [identifier]
else: else:
suffixes = [suffix] identifiers = [identifier]
subsubconfig = SubConfig(option, subsubconfig = SubConfig(option,
index, index,
path, path,
self.config_bag, self.config_bag,
self, self,
suffixes, identifiers,
properties=properties, properties=properties,
validate_properties=validate_properties, validate_properties=validate_properties,
true_path=true_path, true_path=true_path,
@ -363,7 +373,7 @@ class SubConfig:
if index >= length: if index >= length:
raise LeadershipError(_(f'index "{index}" is greater than the leadership ' raise LeadershipError(_(f'index "{index}" is greater than the leadership '
f'length "{length}" for option ' f'length "{length}" for option '
f'"{option.impl_get_display_name(subsubconfig)}"')) f'{option.impl_get_display_name(subsubconfig, with_quote=True)}'))
return subsubconfig return subsubconfig
def get_path(self, def get_path(self,
@ -393,7 +403,7 @@ class SubConfig:
path, path,
cconfig_bag, cconfig_bag,
self, self,
self.suffixes, self.identifiers,
validate_properties=False, validate_properties=False,
) )
self._length = len(cconfig_bag.context.get_value(subconfig)) self._length = len(cconfig_bag.context.get_value(subconfig))
@ -587,7 +597,7 @@ class _Config(CCache):
raise AttributeError(_("no option found in config" raise AttributeError(_("no option found in config"
" with these criteria")) " with these criteria"))
def _walk_valid_value(self, def walk_valid_value(self,
subconfig, subconfig,
only_mandatory, only_mandatory,
): ):
@ -649,19 +659,19 @@ class _Config(CCache):
option = subconfig.option.get_child(name, option = subconfig.option.get_child(name,
config_bag, config_bag,
subconfig, subconfig,
with_suffix=True, with_identifier=True,
allow_dynoption=allow_dynoption, allow_dynoption=allow_dynoption,
) )
if isinstance(option, tuple): if isinstance(option, tuple):
suffix, option = option identifier, option = option
else: else:
suffix = None identifier = None
subconfig = subconfig.get_child(option, subconfig = subconfig.get_child(option,
index_, index_,
validate_properties, validate_properties,
properties=properties, properties=properties,
name=name, name=name,
suffix=suffix, identifier=identifier,
true_path=true_path_, true_path=true_path_,
) )
return subconfig return subconfig
@ -710,7 +720,7 @@ class _Config(CCache):
only_mandatory: bool, only_mandatory: bool,
): ):
try: try:
value = self._walk_valid_value(subconfig, value = self.walk_valid_value(subconfig,
only_mandatory, only_mandatory,
) )
except PropertiesOptionError as err: except PropertiesOptionError as err:
@ -764,8 +774,8 @@ class _Config(CCache):
length = subconfig.parent.get_length_leadership() length = subconfig.parent.get_length_leadership()
follower_len = self.get_values().get_max_length(subconfig.path) follower_len = self.get_values().get_max_length(subconfig.path)
if follower_len > length: if follower_len > length:
option_name = subconfig.option.impl_get_display_name(subconfig) option_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True)
raise LeadershipError(_(f'the follower option "{option_name}" ' raise LeadershipError(_(f'the follower option {option_name} '
f'has greater length ({follower_len}) than the leader ' f'has greater length ({follower_len}) than the leader '
f'length ({length})')) f'length ({length})'))
self.get_settings().validate_mandatory(subconfig, self.get_settings().validate_mandatory(subconfig,