diff --git a/src/rougail/annotator/__init__.py b/src/rougail/annotator/__init__.py index c22fe358..28563885 100644 --- a/src/rougail/annotator/__init__.py +++ b/src/rougail/annotator/__init__.py @@ -1,3 +1,5 @@ +"""Annotate dictionaries +""" from .group import GroupAnnotator from .service import ServiceAnnotator, ERASED_ATTRIBUTES from .variable import VariableAnnotator, CONVERT_OPTION diff --git a/src/rougail/annotator/constrainte.py b/src/rougail/annotator/constrainte.py index 4a62c9e2..51bce614 100644 --- a/src/rougail/annotator/constrainte.py +++ b/src/rougail/annotator/constrainte.py @@ -74,27 +74,28 @@ class ConstrainteAnnotator: only if FREEZE_AUTOFREEZE_VARIABLE == 'oui' this variable is frozen """ def _convert_auto_freeze(variable): - if variable.auto_freeze: - if variable.namespace != Config['variable_namespace']: - xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) - msg = _(f'auto_freeze is not allowed in extra "{variable.namespace}" in {xmlfiles}') - raise DictConsistencyError(msg, 49) - new_condition = self.objectspace.condition(variable.xmlfiles) - new_condition.name = 'auto_frozen_if_not_in' - new_condition.namespace = variable.namespace - new_condition.source = FREEZE_AUTOFREEZE_VARIABLE - new_param = self.objectspace.param(variable.xmlfiles) - new_param.text = 'oui' - new_condition.param = [new_param] - new_target = self.objectspace.target(variable.xmlfiles) - new_target.type = 'variable' - new_target.name = variable.name - new_condition.target = [new_target] - if not hasattr(self.objectspace.space, 'constraints'): - self.objectspace.space.constraints = self.objectspace.constraints(variable.xmlfiles) - if not hasattr(self.objectspace.space.constraints, 'condition'): - self.objectspace.space.constraints.condition = [] - self.objectspace.space.constraints.condition.append(new_condition) + if not variable.auto_freeze: + return + if variable.namespace != Config['variable_namespace']: + xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) + msg = _(f'auto_freeze is not allowed in extra "{variable.namespace}" in {xmlfiles}') + raise DictConsistencyError(msg, 49) + new_condition = self.objectspace.condition(variable.xmlfiles) + new_condition.name = 'auto_frozen_if_not_in' + new_condition.namespace = variable.namespace + new_condition.source = FREEZE_AUTOFREEZE_VARIABLE + new_param = self.objectspace.param(variable.xmlfiles) + new_param.text = 'oui' + new_condition.param = [new_param] + new_target = self.objectspace.target(variable.xmlfiles) + new_target.type = 'variable' + new_target.name = variable.name + new_condition.target = [new_target] + if not hasattr(self.objectspace.space, 'constraints'): + self.objectspace.space.constraints = self.objectspace.constraints(variable.xmlfiles) + if not hasattr(self.objectspace.space.constraints, 'condition'): + self.objectspace.space.constraints.condition = [] + self.objectspace.space.constraints.condition.append(new_condition) for variables in self.objectspace.space.variables.values(): for family in variables.family.values(): if not hasattr(family, 'variable'): diff --git a/src/rougail/annotator/family.py b/src/rougail/annotator/family.py index 8a494647..7951127e 100644 --- a/src/rougail/annotator/family.py +++ b/src/rougail/annotator/family.py @@ -1,3 +1,5 @@ +"""Annotate family +""" from ..i18n import _ from ..error import DictConsistencyError @@ -7,147 +9,174 @@ modes_level = ('basic', 'normal', 'expert') class Mode: - def __init__(self, name, level): + """Class to manage mode level + """ + def __init__(self, + name: str, + level: int, + ) -> None: self.name = name self.level = level - def __gt__(self, other): + + def __gt__(self, + other: int, + ) -> bool: return other.level < self.level -def mode_factory(): - mode_obj = {} - for idx in range(len(modes_level)): - name = modes_level[idx] - mode_obj[name] = Mode(name, idx) - return mode_obj - -modes = mode_factory() +modes = {name: Mode(name, idx) for idx, name in enumerate(modes_level)} class FamilyAnnotator: + """Annotate family + """ def __init__(self, objectspace, ): self.objectspace = objectspace - self.remove_empty_families() - self.change_variable_mode() - self.change_family_mode() - self.dynamic_families() - - def remove_empty_families(self): # pylint: disable=C0111,R0201 if hasattr(self.objectspace.space, 'variables'): - for family in self.objectspace.space.variables.values(): - if hasattr(family, 'family'): - space = family.family - removed_families = [] - for family_name, sfamily in space.items(): - if not hasattr(sfamily, 'variable') or len(sfamily.variable) == 0: - removed_families.append(family_name) - for family_name in removed_families: - del space[family_name] + self.remove_empty_families() + self.change_variable_mode() + self.change_family_mode() + self.dynamic_families() - def change_family_mode(self): # pylint: disable=C0111 - if not hasattr(self.objectspace.space, 'variables'): - return - for family in self.objectspace.space.variables.values(): - if hasattr(family, 'family'): - for vfamily in family.family.values(): - mode = modes_level[-1] - for variable in vfamily.variable.values(): - if isinstance(variable, self.objectspace.leadership): - variable_mode = variable.variable[0].mode - variable.variable[0].mode = None - variable.mode = variable_mode - else: - variable_mode = variable.mode - if variable_mode is not None and modes[mode] > modes[variable_mode]: - mode = variable_mode - vfamily.mode = mode + def remove_empty_families(self): + """Remove all families without any variable + """ + for families in self.objectspace.space.variables.values(): + removed_families = [] + for family_name, family in families.family.items(): + if not hasattr(family, 'variable') or len(family.variable) == 0: + removed_families.append(family_name) + for family_name in removed_families: + del families.family[family_name] - def dynamic_families(self): # pylint: disable=C0111 - if not hasattr(self.objectspace.space, 'variables'): - return - for family in self.objectspace.space.variables.values(): - if hasattr(family, 'family'): - for vfamily in family.family.values(): - if 'dynamic' in vars(vfamily): - namespace = self.objectspace.paths.get_variable_namespace(vfamily.dynamic) - varpath = self.objectspace.paths.get_variable_path(vfamily.dynamic, namespace) - obj = self.objectspace.paths.get_variable_obj(varpath) - if not obj.multi: - xmlfiles = self.objectspace.display_xmlfiles(vfamily.xmlfiles) - raise DictConsistencyError(_(f'dynamic family "{vfamily.name}" must be linked to multi variable in {xmlfiles}'), 16) - vfamily.dynamic = varpath + def change_variable_mode(self): + """change the mode of variables + """ + for variables in self.objectspace.space.variables.values(): + for family in variables.family.values(): + family_mode = family.mode + for variable in family.variable.values(): + if not isinstance(variable, self.objectspace.leadership): + func = self._change_variabe_mode + else: + func = self._change_variable_mode_leader + func(variable, + family_mode, + ) - def annotate_variable(self, - variable, - family_mode: str, - is_follower=False, - ) -> None: - # if the variable is mandatory and doesn't have any value - # then the variable's mode is set to 'basic' + def _change_variabe_mode(self, + variable, + family_mode: str, + ) -> None: + # auto_save or auto_freeze variable is set to 'basic' mode + # if its mode is not defined by the user + if 'mode' not in vars(variable) and \ + (variable.auto_save is True or variable.auto_freeze is True): + variable.mode = modes_level[0] + self._annotate_variable(variable, + family_mode, + ) + + def _change_variable_mode_leader(self, + leadership, + family_mode: str, + ) -> None: + is_follower = False + leader_mode = None + for follower in leadership.variable: + if follower.auto_save is True: + xmlfiles = self.objectspace.display_xmlfiles(leadership.xmlfiles) + msg = _(f'leader/followers "{follower.name}" could not be auto_save in {xmlfiles}') + raise DictConsistencyError(msg, 29) + if follower.auto_freeze is True: + xmlfiles = self.objectspace.display_xmlfiles(leadership.xmlfiles) + msg = f'leader/followers "{follower.name}" could not be auto_freeze in {xmlfiles}' + raise DictConsistencyError(_(msg), 30) + self._annotate_variable(follower, + family_mode, + is_follower, + ) + if leader_mode is None: + leader_mode = leadership.variable[0].mode + leadership.variable[0].mode = None + else: + # leader's mode is minimum level + if modes[leader_mode] > modes[follower.mode]: + follower.mode = leader_mode + is_follower = True + leadership.mode = leader_mode + + def _annotate_variable(self, + variable, + family_mode: str, + is_follower=False, + ) -> None: + """if the variable is mandatory and doesn't have any value + then the variable's mode is set to 'basic' + """ + # a boolean must have value, the default value is "True" if not hasattr(variable, 'value') and variable.type == 'boolean': new_value = self.objectspace.value(variable.xmlfiles) new_value.name = True new_value.type = 'boolean' variable.value = [new_value] + # variable with default value is mandatory if hasattr(variable, 'value') and variable.value: has_value = True for value in variable.value: if value.type == 'calculation': has_value = False - if hasattr(value, 'param'): - for param in value.param: - if param.type == 'variable': - break + break if has_value: - # if has value but without any calculation + # if has value without any calculation variable.mandatory = True + # mandatory variable without value is a basic variable if variable.mandatory is True and (not hasattr(variable, 'value') or is_follower): variable.mode = modes_level[0] - if variable.mode is not None and modes[variable.mode] < modes[family_mode] and (not is_follower or variable.mode != modes_level[0]): + # none basic variable in high level family has to be in high level + if modes[variable.mode] < modes[family_mode] and \ + (not is_follower or variable.mode != modes_level[0]): variable.mode = family_mode + # hidden variable is also frozen if variable.hidden is True: variable.frozen = True - if not variable.auto_save is True and \ + if not variable.auto_save and \ not variable.auto_freeze and \ 'force_default_on_freeze' not in vars(variable): variable.force_default_on_freeze = True - def change_variable_mode(self): # pylint: disable=C0111 - if not hasattr(self.objectspace.space, 'variables'): - return - for variables in self.objectspace.space.variables.values(): - if hasattr(variables, 'family'): - for family in variables.family.values(): - family_mode = family.mode - if hasattr(family, 'variable'): - for variable in family.variable.values(): + def change_family_mode(self): + """change mode of a family + """ + for families in self.objectspace.space.variables.values(): + for family in families.family.values(): + # default is high level + mode = modes_level[-1] + # get de lower sub variable mode + for variable in family.variable.values(): + variable_mode = variable.mode + if modes[mode] > modes[variable_mode]: + mode = variable_mode + # set the lower variable mode to family + family.mode = mode - if isinstance(variable, self.objectspace.leadership): - for idx, follower in enumerate(variable.variable): - if follower.auto_save is True: - xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) - raise DictConsistencyError(_(f'leader/followers "{follower.name}" could not be auto_save in {xmlfiles}'), 29) - if follower.auto_freeze is True: - xmlfiles = self.objectspace.display_xmlfiles(variable.xmlfiles) - raise DictConsistencyError(_(f'leader/followers "{follower.name}" could not be auto_freeze in {xmlfiles}'), 30) - is_follower = idx != 0 - self.annotate_variable(follower, - family_mode, - is_follower, - ) - # leader's mode is minimum level - if modes[variable.variable[0].mode] > modes[follower.mode]: - follower.mode = variable.variable[0].mode - variable.mode = variable.variable[0].mode - else: - # auto_save's variable is set in 'basic' mode if its mode is 'normal' - if variable.auto_save is True and variable.mode != modes_level[-1]: - variable.mode = modes_level[0] - # auto_freeze's variable is set in 'basic' mode if its mode is 'normal' - if variable.auto_freeze is True and variable.mode != modes_level[-1]: - variable.mode = modes_level[0] - self.annotate_variable(variable, - family_mode, - ) + def dynamic_families(self): + """link dynamic families to object + """ + for families in self.objectspace.space.variables.values(): + for family in families.family.values(): + if 'dynamic' not in vars(family): + continue + namespace = self.objectspace.paths.get_variable_namespace(family.dynamic) + varpath = self.objectspace.paths.get_variable_path(family.dynamic, + namespace, + ) + obj = self.objectspace.paths.get_variable_obj(varpath) + if not obj.multi: + xmlfiles = self.objectspace.display_xmlfiles(family.xmlfiles) + msg = _(f'dynamic family "{family.name}" must be linked ' + f'to multi variable in {xmlfiles}') + raise DictConsistencyError(msg, 16) + family.dynamic = varpath