using System.Text; using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Mediator.Contracts; using Elsa.Python.Contracts; using Elsa.Python.Models; using Elsa.Python.Notifications; using Python.Runtime; namespace Elsa.Python.Services; /// /// Evaluates Python expressions using IronPython. /// public class PythonNetPythonEvaluator : IPythonEvaluator { private const string ReturnVarName = "elsa_python_result_variable_name"; private readonly INotificationSender _notificationSender; /// /// Initializes a new instance of the class. /// public PythonNetPythonEvaluator(INotificationSender notificationSender) { _notificationSender = notificationSender; } /// public async Task EvaluateAsync(string expression, Type returnType, ExpressionExecutionContext context, CancellationToken cancellationToken = default) { using var gil = Py.GIL(); using var scope = Py.CreateScope(); var notification = new EvaluatingPython(scope, context); scope.Import("System"); // Add globals. scope.Set("execution_context", new ExecutionContextProxy(context)); scope.Set("input", new InputProxy(context)); scope.Set("output", new OutputProxy(context)); scope.Set("outcome", new OutcomeProxy(context)); await _notificationSender.SendAsync(notification, cancellationToken); var wrappedScript = WrapInExecuteScriptFunction(expression); scope.Exec(wrappedScript); var result = scope.Get(ReturnVarName); return result.ConvertTo(returnType); } /// /// Wraps the user script in a function called execute_script() and returns the result of that function. /// private static string WrapInExecuteScriptFunction(string userScript, int indentationLevel = 1) { var lines = userScript.Split('\n'); var wrappedScript = new StringBuilder(); var indentation = new string(' ', 4 * indentationLevel); wrappedScript.AppendLine("def execute_script():"); foreach (var line in lines.Take(lines.Length - 1)) wrappedScript.AppendLine(indentation + line); var lastLine = lines.LastOrDefault() ?? ""; if (!lastLine.StartsWith("return")) lastLine = $"return {lastLine}"; wrappedScript.AppendLine(indentation + lastLine); wrappedScript.AppendLine($"{ReturnVarName} = execute_script()"); return wrappedScript.ToString(); } }