From 7ae1b48f4af95afbc9c8b6d3d7fb4f4da4a7016f Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 22 Oct 2024 11:04:57 +0200 Subject: [PATCH] fix: better permissive support --- tiramisu/api.py | 84 +++++++++++++++++++++++--------- tiramisu/autolib.py | 114 ++++++++++++++++++++++++-------------------- tiramisu/config.py | 104 ++++++++++++++++++++++------------------ 3 files changed, 179 insertions(+), 123 deletions(-) diff --git a/tiramisu/api.py b/tiramisu/api.py index 3c0ec22..2f2908a 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -21,7 +21,7 @@ from functools import wraps from copy import deepcopy -from .error import ConfigError, LeadershipError, ValueErrorWarning +from .error import ConfigError, LeadershipError, ValueErrorWarning, PropertiesOptionError from .i18n import _ from .setting import ConfigBag, owners, groups, undefined, \ FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, \ @@ -92,12 +92,13 @@ class CommonTiramisu(TiramisuHelp): _allow_dynoption = False def _set_subconfig(self) -> None: - self._subconfig = self._config_bag.context.get_sub_config(self._config_bag, - self._path, - self._index, - validate_properties=False, - allow_dynoption=self._allow_dynoption, - ) + 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, + ) def option_type(typ): @@ -119,6 +120,7 @@ def option_type(typ): )] kwargs['is_group'] = True return func(self, options_bag, *args[1:], **kwargs) + self._set_subconfig() option = self._subconfig.option error_type = None if 'dynamic' in types: @@ -194,6 +196,7 @@ class CommonTiramisuOption(CommonTiramisu): self._path = path self._index = index self._config_bag = config_bag + self._subconfig = None self._set_subconfig() @@ -292,6 +295,11 @@ class _TiramisuOptionOptionDescription: return 'optiondescription' 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']) def isdynamic(self, *, @@ -342,19 +350,19 @@ class _TiramisuOptionOptionDescription: return ret @option_type(['dynamic', 'with_or_without_index']) - def suffixes(self, - only_self: bool=False, - uncalculated: bool=False, - ): - """Get suffixes for dynamic option""" + def identifiers(self, + only_self: bool=False, + uncalculated: bool=False, + ): + """Get identifiers for dynamic option""" if not only_self: - return self._subconfig.suffixes + return self._subconfig.identifiers if not self._subconfig.option.impl_is_optiondescription() or \ 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')) - return self._subconfig.option.get_suffixes(self._subconfig.parent, - uncalculated=uncalculated, - ) + 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_identifiers(self._subconfig.parent, + uncalculated=uncalculated, + ) class _TiramisuOptionOption(_TiramisuOptionOptionDescription): @@ -571,6 +579,7 @@ class TiramisuOptionPermissive(CommonTiramisuOption): class TiramisuOptionInformation(CommonTiramisuOption): """Manage option's informations""" _validate_properties = False + _allow_dynoption = True @option_type(['option', 'optiondescription', 'with_or_without_index', 'symlink']) def get(self, @@ -670,8 +679,8 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet): option = self._subconfig.option if not isinstance(value, Calculation) and option.impl_is_leader() and \ len(value) < self._subconfig.parent.get_length_leadership(): - raise LeadershipError(_('cannot reduce length of the leader "{}"' - '').format(option.impl_get_display_name(self._subconfig))) + raise LeadershipError(_('cannot reduce length of the leader {}' + '').format(option.impl_get_display_name(self._subconfig, with_quote=True))) values = self._config_bag.context.get_values() return values.set_value(self._subconfig, value @@ -753,6 +762,32 @@ class TiramisuOptionValue(CommonTiramisuOption, _TiramisuODGet): """Length for a 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], prefix: str, @@ -808,10 +843,7 @@ class TiramisuOption(CommonTiramisu, self._index = index self._config_bag = config_bag self._allow_dynoption = allow_dynoption - if subconfig is None: - self._set_subconfig() - else: - self._subconfig = subconfig + self._subconfig = subconfig if not self._registers: _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}')) # def __iter__(self): + self._set_subconfig() for sub_subconfig in self._subconfig.get_children(True): yield TiramisuOption(sub_subconfig.path, sub_subconfig.index, @@ -841,6 +874,7 @@ class TiramisuOption(CommonTiramisu, @option_type('optiondescription') def group_type(self): """Get type for an optiondescription (only for optiondescription)""" + self._set_subconfig() return self._subconfig.option.impl_get_group_type() @option_type('optiondescription') @@ -850,6 +884,7 @@ class TiramisuOption(CommonTiramisu, uncalculated: bool=False, ): """List options inside an option description (by default list only option)""" + self._set_subconfig() return self._list(self._subconfig, validate_properties, uncalculated=uncalculated, @@ -1335,12 +1370,15 @@ class TiramisuContextOption(TiramisuConfig, _TiramisuOptionWalk): return 'optiondescription' def list(self, + *, validate_properties: bool=True, + uncalculated: bool=False, ): """List options (by default list only option)""" root = self._config_bag.context.get_root(self._config_bag) return self._list(root, validate_properties, + uncalculated=uncalculated, ) def _load_dict(self, diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index f411b28..d8118cb 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -115,12 +115,12 @@ class ParamOption(Param): class ParamDynOption(ParamOption): - __slots__ = ('suffixes', + __slots__ = ('identifiers', 'optional', ) def __init__(self, option: 'Option', - suffixes: list[str], + identifiers: list[str], notraisepropertyerror: bool=False, raisepropertyerror: bool=False, optional: bool=False, @@ -129,7 +129,11 @@ class ParamDynOption(ParamOption): notraisepropertyerror, 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 @@ -212,12 +216,12 @@ class ParamIndex(Param): __slots__ = tuple() -class ParamSuffix(Param): - __slots__ = ('suffix_index',) +class ParamIdentifier(Param): + __slots__ = ('identifier_index',) def __init__(self, - suffix_index: int=-1, + identifier_index: int=-1, ) -> None: - self.suffix_index = suffix_index + self.identifier_index = identifier_index class Calculation: @@ -345,7 +349,7 @@ def manager_callback(callback: Callable, properties=undefined, ): 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 = [] for idx in range(subconfig.parent.get_length_leadership()): 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 if isinstance(param, ParamSelfOption) or param.notraisepropertyerror or param.raisepropertyerror: 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: - display_name = subconfig.option.impl_get_display_name(subconfig) - raise ValueError(_('the option "{0}" is used in a calculation but is invalid ({1})').format(display_name, err)) from err + 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 except AttributeError as err: if isinstance(param, ParamDynOption) and param.optional: # cannot acces, simulate a propertyerror @@ -385,8 +390,8 @@ def manager_callback(callback: Callable, ['configerror'], config_bag.context.get_settings(), ) - display_name = subconfig.option.impl_get_display_name(subconfig) - raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err + 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 return value def get_option_bag(config_bag, @@ -398,16 +403,13 @@ def manager_callback(callback: Callable, properties=undefined, ): # don't validate if option is option that we tried to validate - config_bag = config_bag.copy() if for_settings: config_bag.properties = config_bag.properties - {'warnings'} - config_bag.set_permissive() if not for_settings: config_bag.properties -= {'warnings'} if self_calc: config_bag.unrestraint() config_bag.remove_validation() -# root = config_bag.context.get_root(config_bag) try: subsubconfig = config_bag.context.get_sub_config(config_bag, 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 if param.notraisepropertyerror or param.raisepropertyerror: raise err from err - display_name = option.impl_get_display_name(subconfig) - raise ConfigError(_('unable to carry out a calculation for "{}", {}').format(display_name, err)) from err + 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 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: if isinstance(param, ParamDynOption) and param.optional: # cannot acces, simulate a propertyerror @@ -430,8 +433,8 @@ def manager_callback(callback: Callable, ['configerror'], config_bag.context.get_settings(), ) - display_name = option.impl_get_display_name(subconfig) - raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err + 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 return subsubconfig if isinstance(param, ParamValue): @@ -447,7 +450,9 @@ def manager_callback(callback: Callable, true_path=subconfig.path, ) 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: isubconfig = get_option_bag(config_bag, param.option, @@ -464,17 +469,17 @@ def manager_callback(callback: Callable, param.default_value, ) except ValueError as err: - display_name = option.impl_get_display_name(subconfig) - raise ConfigError(_(f'unable to get value for calculating "{display_name}", {err}')) from err + 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 if isinstance(param, ParamIndex): 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()): - display_name = subconfig.option.impl_get_display_name(subconfig) - raise ConfigError(_(f'option "{display_name}" is not a dynoptiondescription or in a dynoptiondescription')) - return subconfig.suffixes[param.suffix_index] + 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')) + return subconfig.identifiers[param.identifier_index] if isinstance(param, ParamSelfOption): value = calc_self(param, @@ -490,7 +495,6 @@ def manager_callback(callback: Callable, if isinstance(param, ParamOption): callbk_option = param.option - config_bag = subconfig.config_bag if index is not None and callbk_option.impl_get_leadership() and \ callbk_option.impl_get_leadership().in_same_leadership(option): if not callbk_option.impl_is_follower(): @@ -506,8 +510,7 @@ def manager_callback(callback: Callable, with_index = False if callbk_option.issubdyn(): if isinstance(param, ParamDynOption): - #callbk_option = callbk_option.to_sub_dyoption(param.suffixes) - suffixes = param.suffixes.copy() + identifiers = param.identifiers.copy() paths = callbk_option.impl_getpath().split('.') parents = [config_bag.context.get_root(config_bag)] subconfigs_is_a_list = False @@ -520,9 +523,18 @@ def manager_callback(callback: Callable, allow_dynoption=True, ) if doption.impl_is_dynoptiondescription(): - if suffixes: - suffix = suffixes.pop(0) - name = doption.impl_getname(suffix) + if not identifiers: + identifier = None + 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: doption = parent.option.get_child(name, config_bag, @@ -534,14 +546,8 @@ def manager_callback(callback: Callable, None, True, name=name, - suffix=suffix, + identifier=identifier, )) - else: - subconfigs_is_a_list = True - new_parents.extend(parent.dyn_to_subconfig(doption, - True, - ) - ) else: new_parents.append(parent.get_child(doption, None, @@ -625,11 +631,13 @@ def carry_out_calculation(subconfig: 'SubConfig', Values could have multiple values only when key is ''.""" option = subconfig.option 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): return ((None, i) for i in iterator) args = [] kwargs = {} + config_bag = config_bag.copy() + config_bag.set_permissive() if callback_params: for key, param in chain(fake_items(callback_params.args), callback_params.kwargs.items()): try: @@ -667,18 +675,18 @@ def carry_out_calculation(subconfig: 'SubConfig', if args or kwargs: raise LeadershipError(_('the "{}" function with positional arguments "{}" ' 'and keyword arguments "{}" must not return ' - 'a list ("{}") for the follower option "{}"' + 'a list ("{}") for the follower option {}' '').format(callback.__name__, args, kwargs, ret, - option.impl_get_display_name(subconfig))) + option.impl_get_display_name(subconfig, with_quote=True))) else: raise LeadershipError(_('the "{}" function must not return a list ("{}") ' - 'for the follower option "{}"' + 'for the follower option {}' '').format(callback.__name__, ret, - option.impl_get_display_name(subconfig))) + option.impl_get_display_name(subconfig, with_quote=True))) return ret @@ -702,22 +710,22 @@ def calculate(subconfig, if allow_value_error: if force_value_warning: raise ValueWarning(str(err)) - raise err + raise err from err error = err + except ConfigError as err: + raise err from err except Exception as err: - import traceback - traceback.print_exc() error = err if args or kwargs: 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__, - subconfig.option.impl_get_display_name(subconfig), + subconfig.option.impl_get_display_name(subconfig, with_quote=True), args, kwargs) else: - msg = _('unexpected error "{0}" in function "{1}" for option "{2}"' + msg = _('unexpected error "{0}" in function "{1}" for option {2}' '').format(str(error), callback.__name__, - subconfig.option.impl_get_display_name(subconfig)) + subconfig.option.impl_get_display_name(subconfig, with_quote=True)) raise ConfigError(msg) from error diff --git a/tiramisu/config.py b/tiramisu/config.py index f7651dc..6c2e645 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -182,10 +182,11 @@ class SubConfig: 'index', 'path', 'true_path', - 'properties', + '_properties', + 'apply_requires', 'transitive_properties', 'is_dynamic', - 'suffixes', + 'identifiers', '_length', ) def __init__(self, @@ -194,14 +195,14 @@ class SubConfig: path: str, config_bag: ConfigBag, parent: Optional['SubConfig'], - suffixes: Optional[list[str]], + identifiers: Optional[list[str]], *, true_path: Optional[str]=None, properties: Union[list[str], undefined]=undefined, validate_properties: bool=True, ) -> None: self.index = index - self.suffixes = suffixes + self.identifiers = identifiers self.option = option self.config_bag = config_bag self.parent = parent @@ -210,38 +211,47 @@ class SubConfig: if true_path is None: true_path = path 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 - settings = config_bag.context.get_settings() if parent and parent.is_dynamic or self.option.impl_is_dynoptiondescription(): self.is_dynamic = True else: self.is_dynamic = False - if properties is undefined: - if path is None: - self.properties = frozenset() - else: - self.properties = frozenset() - if validate_properties: - self.properties = settings.getproperties(self, - apply_requires=False, - ) - self.config_bag.context.get_settings().validate_properties(self) - self.properties = settings.getproperties(self, - apply_requires=apply_requires, - ) - else: - self.properties = properties + self._properties = properties if validate_properties: + if self.path and self._properties is undefined: + settings = config_bag.context.get_settings() + self._properties = settings.getproperties(self, + apply_requires=False, + ) + self.config_bag.context.get_settings().validate_properties(self) + self._properties = undefined 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: + settings = config_bag.context.get_settings() self.transitive_properties = settings.calc_transitive_properties(self, self.properties, ) else: 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): return f'' @@ -253,9 +263,9 @@ class SubConfig: true_path: Optional[str]=None, ) -> List['SubConfig']: config_bag = self.config_bag - for suffix in child.get_suffixes(self): + for identifier in child.get_identifiers(self): try: - name = child.impl_getname(suffix) + name = child.impl_getname(identifier) if not validate_properties: properties = None else: @@ -263,7 +273,7 @@ class SubConfig: yield self.get_child(child, None, validate_properties, - suffix=suffix, + identifier=identifier, name=name, properties=properties, true_path=true_path, @@ -323,7 +333,7 @@ class SubConfig: *, properties=undefined, allow_dynoption: bool=False, - suffix: Optional[str]=None, + identifier: Optional[str]=None, name: Optional[str]=None, check_index: bool=True, config_bag: ConfigBag=None, @@ -338,19 +348,19 @@ class SubConfig: path = self.get_path(name, option, ) - if suffix is None: - suffixes = self.suffixes + if identifier is None: + identifiers = self.identifiers else: - if self.suffixes: - suffixes = self.suffixes + [suffix] + if self.identifiers: + identifiers = self.identifiers + [identifier] else: - suffixes = [suffix] + identifiers = [identifier] subsubconfig = SubConfig(option, index, path, self.config_bag, self, - suffixes, + identifiers, properties=properties, validate_properties=validate_properties, true_path=true_path, @@ -363,7 +373,7 @@ class SubConfig: if index >= length: raise LeadershipError(_(f'index "{index}" is greater than the leadership ' f'length "{length}" for option ' - f'"{option.impl_get_display_name(subsubconfig)}"')) + f'{option.impl_get_display_name(subsubconfig, with_quote=True)}')) return subsubconfig def get_path(self, @@ -393,7 +403,7 @@ class SubConfig: path, cconfig_bag, self, - self.suffixes, + self.identifiers, validate_properties=False, ) self._length = len(cconfig_bag.context.get_value(subconfig)) @@ -587,10 +597,10 @@ class _Config(CCache): raise AttributeError(_("no option found in config" " with these criteria")) - def _walk_valid_value(self, - subconfig, - only_mandatory, - ): + def walk_valid_value(self, + subconfig, + only_mandatory, + ): value = self.get_value(subconfig, need_help=False, ) @@ -649,19 +659,19 @@ class _Config(CCache): option = subconfig.option.get_child(name, config_bag, subconfig, - with_suffix=True, + with_identifier=True, allow_dynoption=allow_dynoption, ) if isinstance(option, tuple): - suffix, option = option + identifier, option = option else: - suffix = None + identifier = None subconfig = subconfig.get_child(option, index_, validate_properties, properties=properties, name=name, - suffix=suffix, + identifier=identifier, true_path=true_path_, ) return subconfig @@ -710,9 +720,9 @@ class _Config(CCache): only_mandatory: bool, ): try: - value = self._walk_valid_value(subconfig, - only_mandatory, - ) + value = self.walk_valid_value(subconfig, + only_mandatory, + ) except PropertiesOptionError as err: if err.proptype in (['mandatory'], ['empty']): if only_mandatory: @@ -764,8 +774,8 @@ class _Config(CCache): length = subconfig.parent.get_length_leadership() follower_len = self.get_values().get_max_length(subconfig.path) if follower_len > length: - option_name = subconfig.option.impl_get_display_name(subconfig) - raise LeadershipError(_(f'the follower option "{option_name}" ' + option_name = subconfig.option.impl_get_display_name(subconfig, with_quote=True) + raise LeadershipError(_(f'the follower option {option_name} ' f'has greater length ({follower_len}) than the leader ' f'length ({length})')) self.get_settings().validate_mandatory(subconfig,