| import os |
| import ast |
| import io |
| import sys |
| import numpy as np |
| import pandas as pd |
| import scipy |
|
|
| ALLOWED_MODULES = {"numpy", "pandas", "scipy"} |
|
|
| def interpret_python_math_code(python_code: str) -> str: |
| """ |
| Interprets a string of Python code to perform math calculations. |
| |
| Security Note: This function uses exec(). While it attempts to restrict |
| imports to numpy, pandas, and scipy, and runs with a restricted |
| global scope, executing arbitrary code always carries risks. Ensure |
| that input code is from a trusted source or properly sanitized. |
| |
| The code must only import modules from the allowed list: numpy, pandas, scipy. |
| Submodules of these (e.g., numpy.linalg, scipy.stats) are permitted. |
| For example: |
| 'import numpy as np' is allowed. |
| 'from scipy.stats import norm' is allowed. |
| 'import os' is NOT allowed. |
| |
| To return a result, the code should either: |
| 1. End with an expression (e.g., '1 + 1' or 'np.array([1,2,3]).sum()'). |
| 2. Assign the result to a variable named '_result' (e.g., '_result = my_calculation'). |
| |
| Print statements will also be captured and returned along with the result. |
| """ |
| |
| try: |
| tree = ast.parse(python_code) |
| for node in tree.body: |
| if isinstance(node, ast.Import): |
| for alias in node.names: |
| root_module = alias.name.split('.')[0] |
| if root_module not in ALLOWED_MODULES: |
| return (f"Error: Import of '{alias.name}' is not allowed. " |
| f"Only modules from {list(ALLOWED_MODULES)} are permitted.") |
| elif isinstance(node, ast.ImportFrom): |
| if node.module: |
| root_module = node.module.split('.')[0] |
| if root_module not in ALLOWED_MODULES: |
| return (f"Error: Import from '{node.module}' is not allowed. " |
| f"Only modules from {list(ALLOWED_MODULES)} are permitted.") |
| except SyntaxError as e: |
| return f"Syntax Error in input code: {e}" |
|
|
| |
| restricted_globals = { |
| "__builtins__": { |
| "print": print, |
| "abs": abs, "round": round, "min": min, "max": max, "sum": sum, "len": len, |
| "range": range, "zip": zip, "enumerate": enumerate, |
| "int": int, "float": float, "str": str, "list": list, "dict": dict, "tuple": tuple, "set": set, |
| "True": True, "False": False, "None": None, |
| "__import__": __import__, |
| } |
| |
| |
| |
| } |
| local_vars = {} |
|
|
| |
| old_stdout = sys.stdout |
| redirected_output = io.StringIO() |
| sys.stdout = redirected_output |
|
|
| |
| calculated_value = None |
| result_source = "" |
| output_str = "" |
|
|
| try: |
| compiled_code = compile(python_code, '<string>', 'exec') |
| exec(compiled_code, restricted_globals, local_vars) |
| |
| |
| if "_result" in local_vars: |
| calculated_value = local_vars["_result"] |
| result_source = "variable '_result'" |
| |
| elif tree.body and isinstance(tree.body[-1], ast.Expr): |
| |
| if isinstance(tree.body[-1].value, ast.AST): |
| last_expr_ast = ast.Expression(body=tree.body[-1].value) |
| |
| compiled_expr = compile(last_expr_ast, '<string>', 'eval') |
| |
| calculated_value = eval(compiled_expr, restricted_globals, local_vars) |
| result_source = "last expression" |
| |
| sys.stdout = old_stdout |
| output_str = redirected_output.getvalue() |
|
|
| if calculated_value is not None: |
| return f"Result (from {result_source}):\n{calculated_value}\n\nCaptured Output:\n{output_str}".strip() |
| else: |
| return f"Executed successfully.\n\nCaptured Output:\n{output_str}\n(No specific result value found via '_result' variable or last expression evaluation.)".strip() |
|
|
| except Exception as e: |
| if sys.stdout == redirected_output: |
| sys.stdout = old_stdout |
| output_str = redirected_output.getvalue() |
| return f"Execution Error: {type(e).__name__}: {e}\n\nCaptured Output:\n{output_str}".strip() |
| finally: |
| |
| if sys.stdout == redirected_output: |
| sys.stdout = old_stdout |
|
|
|
|
| |
| if __name__ == "__main__": |
| code = """ |
| import numpy as np |
| # import os # This should trigger an error since 'os' is not allowed |
| arr = np.array([1, 2, 3, 4, 5]) |
| _result = arr.mean() |
| """ |
| print(interpret_python_math_code(code)) |
|
|