diff --git a/src/rougail/convert/convert.py b/src/rougail/convert/convert.py index 210f984f3..0b6866db7 100644 --- a/src/rougail/convert/convert.py +++ b/src/rougail/convert/convert.py @@ -1000,19 +1000,17 @@ class ParserVariable: calculation_object["namespace"] = self.namespace calculation_object["xmlfiles"] = xmlfiles # + if attribute == "default" and "identifier" in calculation_object: + identifier = calculation_object["identifier"] + if isinstance(identifier, dict): + self.set_calculation(calculation_object, "identifier", identifier, path, family_is_dynamic, xmlfiles, inside_list=inside_list, index=index) if "params" in calculation_object: if not isinstance(calculation_object["params"], dict): raise Exception("params must be a dict") params = [] for key, val in calculation_object["params"].items(): if isinstance(val, dict) and "type" not in val: - # auto set type - param_typ = set(CALCULATION_TYPES) & set(val) - # XXX variable is also set to information - if param_typ == {"variable", "information"}: - param_typ = {"information"} - if len(param_typ) == 1: - val["type"] = list(param_typ)[0] + self.set_param_type(val) if not isinstance(val, dict) or "type" not in val: param_typ = "any" val = { @@ -1064,6 +1062,15 @@ class ParserVariable: else: obj[attribute][index] = calc + def set_param_type(self, val): + # auto set type + param_typ = set(CALCULATION_TYPES) & set(val) + # XXX variable is also set to information + if param_typ == {"variable", "information"}: + param_typ = {"information"} + if len(param_typ) == 1: + val["type"] = list(param_typ)[0] + class RougailConvert(ParserVariable): """Main Rougail conversion""" diff --git a/src/rougail/convert/object_model.py b/src/rougail/convert/object_model.py index 264ed4f7a..d1453b8dd 100644 --- a/src/rougail/convert/object_model.py +++ b/src/rougail/convert/object_model.py @@ -81,8 +81,8 @@ def get_convert_option_types(): class Param(BaseModel): - key: str - namespace: Optional[str] + key: StrictStr + namespace: Optional[StrictStr] model_config = ConfigDict(extra="forbid") def __init__( @@ -102,13 +102,13 @@ class Param(BaseModel): class AnyParam(Param): - type: str + type: StrictStr value: Union[BASETYPE, List[BASETYPE]] class VariableParam(Param): - type: str - variable: str + type: StrictStr + variable: StrictStr propertyerror: bool = True whole: bool = False # dynamic: bool = True @@ -151,7 +151,7 @@ class VariableParam(Param): class IdentifierParam(Param): - type: str + type: StrictStr identifier: Optional[int] = None def __init__( @@ -167,9 +167,9 @@ class IdentifierParam(Param): class InformationParam(Param): - type: str - information: str - variable: Optional[str] = None + type: StrictStr + information: StrictStr + variable: Optional[StrictStr] = None def to_param( self, attribute_name, objectspace, path, version, namespace, xmlfiles @@ -202,7 +202,7 @@ class InformationParam(Param): class IndexParam(Param): - type: str + type: StrictStr def to_param( self, attribute_name, objectspace, path, version, namespace, xmlfiles @@ -220,8 +220,8 @@ class IndexParam(Param): class NamespaceParam(Param): - type: str - namespace: str + type: StrictStr + namespace: StrictStr def to_param( self, attribute_name, objectspace, path, version, namespace, xmlfiles @@ -247,15 +247,15 @@ PARAM_TYPES = { class Calculation(BaseModel): - # path: str + # path: StrictStr inside_list: bool - version: str - ori_path: Optional[str] = None + version: StrictStr + ori_path: Optional[StrictStr] = None default_values: Any = None - namespace: Optional[str] + namespace: Optional[StrictStr] warnings: Optional[bool] = None description: Optional[StrictStr] = None - xmlfiles: List[str] + xmlfiles: List[StrictStr] model_config = ConfigDict(extra="forbid") @@ -492,6 +492,7 @@ class _VariableCalculation(Calculation): propertyerror: bool = True, allow_none: bool = False optional: bool = False + identifier: Optional[Calculation] = None def get_variable( self, @@ -540,6 +541,15 @@ class _VariableCalculation(Calculation): variable_in_calculation_identifier: Optional[str], key: str = None, ): + identifier_is_a_calculation = False + if self.identifier: + if variable_in_calculation_identifier: + msg = _( + 'variable "{0}" has an attribute "{1}" with an identifier "{2}" but the path has also the identifier "{3}"' + ).format(path, self.attribute_name, self.variable, self.identifier, variable_in_calculation_identifier) + raise DictConsistencyError(msg, 89, self.xmlfiles) + variable_in_calculation_identifier = self.identifier + identifier_is_a_calculation = True if not variable_in_calculation: if not objectspace.force_optional: msg = _( @@ -564,9 +574,10 @@ class _VariableCalculation(Calculation): params["__default_value"] = self.default_values if self.allow_none: params["allow_none"] = True - self.check_multi( - objectspace, path, variable_in_calculation_path, variable_in_calculation - ) + if not identifier_is_a_calculation: + self.check_multi( + objectspace, path, variable_in_calculation_path, variable_in_calculation, + ) if path in objectspace.followers: multi = objectspace.multis[path] == "submulti" else: @@ -718,7 +729,7 @@ class _VariableCalculation(Calculation): class VariableCalculation(_VariableCalculation): - attribute_name: Literal["default", "choices", "dynamic"] + attribute_name: Literal["default", "choices", "dynamic", "identifier"] default: Any = undefined def to_function( @@ -1039,7 +1050,7 @@ SECRET_BASETYPE_CALC = Union[StrictStr, JinjaCalculation] class Family(BaseModel): - name: str + name: StrictStr # informations description: Optional[StrictStr] = None help: Optional[StrictStr] = None @@ -1060,12 +1071,12 @@ class Family(BaseModel): class Dynamic(Family): # None only for format 1.0 - variable: str = None + variable: StrictStr = None dynamic: Union[List[Union[StrictStr, Calculation]], Calculation] class Variable(BaseModel): - name: str + name: StrictStr # user informations description: Optional[StrictStr] = None help: Optional[StrictStr] = None @@ -1075,7 +1086,7 @@ class Variable(BaseModel): test: Optional[list] = None # validations ## type will be set dynamically in `annotator/value.py`, default is None - type: str = None + type: StrictStr = None params: Optional[List[Param]] = None regexp: Optional[StrictStr] = None choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None @@ -1094,21 +1105,21 @@ class Variable(BaseModel): disabled: Union[bool, Calculation] = False frozen: Union[bool, Calculation] = False # others - path: str - namespace: Optional[str] - version: str - xmlfiles: List[str] = [] + path: StrictStr + namespace: Optional[StrictStr] + version: StrictStr + xmlfiles: List[StrictStr] = [] model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True) class SymLink(BaseModel): type: Literal["symlink"] = "symlink" - name: str - path: str + name: StrictStr + path: StrictStr opt: Variable - namespace: Optional[str] - version: str - xmlfiles: List[str] = [] + namespace: Optional[StrictStr] + version: StrictStr + xmlfiles: List[StrictStr] = [] model_config = ConfigDict(extra="forbid") diff --git a/src/rougail/convert/tiramisureflector.py b/src/rougail/convert/tiramisureflector.py index f41522694..9864cbd69 100644 --- a/src/rougail/convert/tiramisureflector.py +++ b/src/rougail/convert/tiramisureflector.py @@ -362,12 +362,16 @@ class Common: ) raise DictConsistencyError(msg, 49, self.elt.xmlfiles) param_type = "ParamDynOption" - identifiers = [] - for ident in identifier: - if isinstance(ident, str): - ident = self.convert_str(ident) - identifiers.append(str(ident)) - params.append("[" + ", ".join(identifiers) + "]") + if isinstance(identifier, Calculation): + identifiers = self.populate_calculation(identifier) + else: + identifiers = [] + for ident in identifier: + if isinstance(ident, str): + ident = self.convert_str(ident) + identifiers.append(str(ident)) + identifiers = "[" + ", ".join(identifiers) + "]" + params.append(identifiers) if param.get("optional", False): params.append("optional=True") else: diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/after.json b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/after.json new file mode 100644 index 000000000..c972e2599 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/after.json @@ -0,0 +1,25 @@ +{ + "rougail.var1": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.var2": { + "owner": "default", + "value": "val1" + }, + "rougail.dynval1.var": { + "owner": "default", + "value": "val1" + }, + "rougail.dynval2.var": { + "owner": "default", + "value": "val2" + }, + "rougail.var3": { + "owner": "default", + "value": "val1" + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/base.json b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/base.json new file mode 100644 index 000000000..593e21e4b --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/base.json @@ -0,0 +1,10 @@ +{ + "rougail.var1": [ + "val1", + "val2" + ], + "rougail.var2": "val1", + "rougail.dynval1.var": "val1", + "rougail.dynval2.var": "val2", + "rougail.var3": "val1" +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/before.json b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/before.json new file mode 100644 index 000000000..c972e2599 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/before.json @@ -0,0 +1,25 @@ +{ + "rougail.var1": { + "owner": "default", + "value": [ + "val1", + "val2" + ] + }, + "rougail.var2": { + "owner": "default", + "value": "val1" + }, + "rougail.dynval1.var": { + "owner": "default", + "value": "val1" + }, + "rougail.dynval2.var": { + "owner": "default", + "value": "val2" + }, + "rougail.var3": { + "owner": "default", + "value": "val1" + } +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/mandatory.json b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/mandatory.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/mandatory.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/read_write.json b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/read_write.json new file mode 100644 index 000000000..593e21e4b --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/makedict/read_write.json @@ -0,0 +1,10 @@ +{ + "rougail.var1": [ + "val1", + "val2" + ], + "rougail.var2": "val1", + "rougail.dynval1.var": "val1", + "rougail.dynval2.var": "val2", + "rougail.var3": "val1" +} diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/base.py b/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/base.py new file mode 100644 index 000000000..57bd00b51 --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/base.py @@ -0,0 +1,19 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_2 = StrOption(name="var1", doc="A suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +option_3 = StrOption(name="var2", doc="A suffix variable2", default="val1", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +option_5 = StrOption(name="var", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_4 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_2)))), children=[option_5], properties=frozenset({"standard"}), informations={'dynamic_variable': 'rougail.var1', 'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml']}) +option_6 = StrOption(name="var3", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_5, Calculation(func['calc_value'], Params((ParamOption(option_3)))))))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3, optiondescription_4, option_6], properties=frozenset({"standard"})) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/no_namespace.py b/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/no_namespace.py new file mode 100644 index 000000000..c369b8a3a --- /dev/null +++ b/tests/dictionaries/60_5family_dynamic_calc_identifier/tiramisu/no_namespace.py @@ -0,0 +1,18 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_1 = StrOption(name="var1", doc="A suffix variable", multi=True, default=["val1", "val2"], default_multi="val1", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +option_2 = StrOption(name="var2", doc="A suffix variable2", default="val1", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +option_4 = StrOption(name="var", doc="A dynamic variable", default=Calculation(func['calc_value'], Params((ParamIdentifier()))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_3 = ConvertDynOptionDescription(name="dyn{{ identifier }}", doc="dyn{{ identifier }}", identifiers=Calculation(func['calc_value'], Params((ParamOption(option_1)))), children=[option_4], properties=frozenset({"standard"}), informations={'dynamic_variable': 'var1', 'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml']}) +option_5 = StrOption(name="var3", doc="A variable calculated", default=Calculation(func['calc_value'], Params((ParamDynOption(option_4, Calculation(func['calc_value'], Params((ParamOption(option_2)))))))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/60_5family_dynamic_calc_identifier/rougail/00-base.yml'], 'type': 'string'}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2, optiondescription_3, option_5])