def export_model_to_json(state: State) -> str: # Get Metadata dictionary metadata = state.get("_metadata") metadata_string = None if metadata: # Change lists of 1 element to a simple value metadata = {k: v[0] if len(v) == 1 else v for k, v in metadata.items()} metadata_string = '"Metadata": ' + json.dumps(metadata, cls=CustomEncoder) print(metadata_string) json_structure: JsonStructureType = OrderedDict({ "Parameters": Parameter, "CodeHierarchies": Hierarchy, "Observers": Observer, "InterfaceTypes": FactorType, "InterfaceTypeConverts": FactorTypesRelationUnidirectionalLinearTransformObservation, "Processors": Processor, "Relationships": OrderedDict({ "PartOf": ProcessorsRelationPartOfObservation, "Upscale": ProcessorsRelationUpscaleObservation, "IsA": ProcessorsRelationIsAObservation, "DirectedFlow": FactorsRelationDirectedFlowObservation }) }) # Get objects from state query = BasicQuery(state) objects = query.execute(values_of_nested_dictionary(json_structure), filt="") json_string = create_json_string_from_objects(objects, json_structure) print(json_string) if metadata_string: json_string = f'{metadata_string}, {json_string}' return f'{{{json_string}}}'
def ast_evaluator(exp: Dict, state: State, obj, issue_lst, evaluation_type="numeric"): """ Numerically evaluate the result of the parse of "expression" rule (not valid for the other "expression" rules) :param exp: Dictionary representing the AST (output of "string_to_ast" function) :param state: "State" used to obtain variables/objects :param obj: An object used when evaluating hierarchical variables. simple names, functions and datasets are considered members of this object :param issue_lst: List in which issues have to be annotated :param evaluation_type: "numeric" for full evaluation, "static" to return True if the expression can be evaluated (explicitly mentioned variables are defined previously) :return: value (scalar EXCEPT for named parameters, which return a tuple "parameter name - parameter value"), list of unresolved variables """ val = None unresolved_vars = set() if "type" in exp: t = exp["type"] if t in ("int", "float", "str", "boolean"): if evaluation_type == "numeric": return exp["value"], unresolved_vars elif evaluation_type == "static": return unresolved_vars elif t == "named_parameter": # This one returns a tuple (parameter name, parameter value, unresolved variables) v, tmp = ast_evaluator(exp["value"], state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp) return exp["param"], v, unresolved_vars elif t == "key_value_list": d = create_dictionary() for k, v in exp["parts"].items(): d[k], tmp = ast_evaluator(v, state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp) return d, unresolved_vars elif t == "dataset": # Function parameters and Slice parameters func_params = [ ast_evaluator(p, state, obj, issue_lst, evaluation_type) for p in exp["func_params"] ] slice_params = [ ast_evaluator(p, state, obj, issue_lst, evaluation_type) for p in exp["slice_params"] ] if evaluation_type == "numeric": # Find dataset named "exp["name"]" if obj is None: # Global dataset ds = state.get(exp["name"], exp["ns"]) if not ds: issue_lst.append( (3, "Global dataset '" + exp["name"] + "' not found")) else: # Dataset inside "obj" try: ds = getattr(obj, exp["name"]) except: ds = None if not ds: issue_lst.append( (3, "Dataset '" + exp["name"] + "' local to " + str(obj) + " not found")) if ds and isinstance(ds, ExternalDataset): return ds.get_data(None, slice_params, None, None, func_params) else: return None elif evaluation_type == "static": # Find dataset named "exp["name"]" if obj is None: # Global dataset ds = state.get(exp["name"], exp["ns"]) if not ds: issue_lst.append( (3, "Global dataset '" + exp["name"] + "' not found")) else: ds = True else: ds = True # We cannot be sure it will be found, but do not break the evaluation # True if the Dataset is True, and the parameters are True return ds and all(func_params) and all(slice_params) elif t == "function": # Call function # First, obtain the Parameters args = [] kwargs = {} can_resolve = True for p in [ ast_evaluator(p, state, obj, issue_lst, evaluation_type) for p in exp["params"] ]: if len(p) == 3: kwargs[p[0]] = p[1] tmp = p[2] else: args.append(p[0]) tmp = p[1] unresolved_vars.update(tmp) if len(tmp) > 0: can_resolve = False if evaluation_type == "numeric": if obj is None: # Check if it can be resolved (all variables specified) # Check if global function exists, then call it. There are no function namespaces (at least for now) if can_resolve and exp["name"] in global_functions: _f = global_functions[exp["name"]] mod_name, func_name = _f["full_name"].rsplit('.', 1) mod = importlib.import_module(mod_name) func = getattr(mod, func_name) if _f["kwargs"]: kwargs.update(_f["kwargs"]) obj = func(*args, *kwargs) else: # Call local function (a "method") try: obj = getattr(obj, exp["name"]) obj = obj(*args, **kwargs) except: obj = None return obj, unresolved_vars elif evaluation_type == "static": if obj is None: # Check if global function exists, then call it. There are no function namespaces (at least for now) if exp["name"] in global_functions: _f = global_functions[exp["name"]] mod_name, func_name = _f["full_name"].rsplit('.', 1) mod = importlib.import_module(mod_name) func = getattr(mod, func_name) # True if everything is True: function defined and all parameters are True obj = func and all(args) and all(kwargs.values()) else: # Call local function (a "method") obj = True return obj elif t == "h_var": # Evaluate in sequence obj = None _namespace = exp.get("ns", None) for o in exp["parts"]: if isinstance(o, str): # Simple name if obj is None: obj = state.get(o, _namespace) if not obj: issue_lst.append( (3, "'" + o + "' is not globally declared in namespace '" + (_namespace if _namespace else "default") + "'")) if _namespace: unresolved_vars.add(_namespace + "::" + o) else: unresolved_vars.add(o) else: if isinstance(obj, ExternalDataset): # Check if "o" is column (measure) or dimension if o in obj.get_columns( ) or o in obj.get_dimensions(): obj = obj.get_data(o, None) else: issue_lst.append(( 3, "'" + o + "' is not a measure or dimension of the dataset." )) else: try: obj = getattr(obj, o) except: issue_lst.append((3, "'" + o + "' is not a .")) else: # Dictionary: function call or dataset access if obj is None: o["ns"] = _namespace obj = ast_evaluator(o, state, obj, issue_lst, evaluation_type) if obj is None or isinstance(obj, (str, int, float, bool)): return obj, unresolved_vars # TODO elif isinstance(obj, ...) depending on core object types, invoke a default method, or # issue ERROR if it is not possible to cast to something simple else: return obj, unresolved_vars elif t == "condition": # Evaluate IF part to a Boolean. If True, return the evaluation of the THEN part; if False, return None if_result, tmp = ast_evaluator(exp["if"], state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp) if len(tmp) == 0: if if_result: then_result, tmp = ast_evaluator(exp["then"], state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp) if len(tmp) > 0: then_result = None return then_result, unresolved_vars else: return None, unresolved_vars elif t == "conditions": for c in exp["parts"]: cond_result, tmp = ast_evaluator(c, state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp) if len(tmp) == 0: if cond_result: return cond_result, unresolved_vars return None, unresolved_vars elif t == "reference": return "[" + exp[ "ref_id"] + "]", unresolved_vars # TODO Return a special type elif t in ("u+", "u-", "multipliers", "adders", "comparison", "not", "and", "or"): # Arithmetic and Boolean # Evaluate recursively the left and right operands if t in ("u+", "u-"): if evaluation_type == "numeric": current = 0 else: current = True else: current, tmp1 = ast_evaluator(exp["terms"][0], state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp1) for i, e in enumerate(exp["terms"][1:]): following, tmp2 = ast_evaluator(e, state, obj, issue_lst, evaluation_type) unresolved_vars.update(tmp2) if len(tmp1) == 0 and len(tmp2) == 0: if evaluation_type == "numeric": # Type casting for primitive types # TODO For Object types, apply default conversion. If both sides are Object, assume number if (isinstance(current, (int, float)) and isinstance(following, (int, float))) or \ (isinstance(current, bool) and isinstance(following, bool)) or \ (isinstance(current, str) and isinstance(following, str)): pass # Do nothing else: # In others cases, CAST to the operand of the left. This may result in an Exception following = type(current)(following) op = exp["ops"][i].lower() if op in ("+", "-", "u+", "u-"): if current is None: current = 0 if following is None: following = 0 if op in ("-", "u-"): following = -following current += following elif op in ("*", "/", "//", "%"): if following is None: following = 1 if current is None: current = 1 if op == "*": current *= following elif op == "/": current /= following elif op == "//": current //= following elif op == "%": current %= following elif op == "not": current = not bool(following) elif op == "and": current = current and following elif op == "or": current = current or following else: # Comparators fn = opMap[op] current = fn(current, following) elif evaluation_type == "static": current = current and following else: current = None # Could not evaluate because there are missing variables return current, unresolved_vars else: issue_lst.append((3, "'type' = " + t + " not supported.")) else: issue_lst.append((3, "'type' not present in " + str(exp))) return val, unresolved_vars