Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/python return enhancement #50

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

MingyueX
Copy link

Instead of returning the textOutputOrError only, this enhancement separated the Error, Output and return value from the executed python script.

In the event of an error in the Python script, the returned map contains:
errorType: Specifies the type of the error (e.g., ValueError, TypeError, etc.).
errorMessage: Provides a detailed error message.
traceback: Offers a traceback of the error, aiding in debugging.

In the event of the Python script finish execution successfully, the returned map contains:
output: Any print statements or other outputs produced by the Python script.
returnValueJson: This key doesn't directly represent the raw return value from the Python script. Instead, it contains a serialized version of the value(s) assigned to the result variable within the executed Python script.

  • If the script produces multiple return values (e.g., in the form of a tuple), each value is serialized individually, and the results are stored as a list in the JSON format.
  • The serialization process is governed by the serialize_to_json function in script.py. By default, it supports basic data types like integers, floats, strings, lists, dictionaries, booleans, and None. If a value doesn't match these types, it's converted to its string representation.

Customization: Users have the flexibility to modify the script.py to suit their needs:

  • Variable Name: The default variable used to capture the return value is result. If a user wishes to use a different variable name, this can be done by adjusting the env.get('result', None) line in the text_thread_run function.
  • Custom Serialization: If the Python script returns complex or custom objects, one can extend the serialize_to_json function to add custom serialization logic.

The script.py is modified for this enhancement accordingly:

import io,os,sys,time,threading,ctypes,inspect,traceback,json

def serialize_to_json(value):
    if isinstance(value, (int, float, str, list, dict, bool, type(None))):
        return value
    # Add more custom serialization logic if needed
    return str(value)

def _async_raise(tid, exctype):
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("Timeout Exception")

def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)

def text_thread_run(code, result_container):
    try:
        env = {}
        exec(code, env, env)
        # Get the value from the 'result' variable
        # Hence to get a return value, assign it to the 'result' variable
        # Change this if you want to use a different variable name
        results = env.get('result', None)
        # The value will be returned to the dart side as a list
        if isinstance(results, tuple):
            serialized_results = [serialize_to_json(item) for item in results]
        else:
            serialized_results = [serialize_to_json(results)]
        result_container[0] = json.dumps(serialized_results)
    except Exception as e:
        result_container[1] = e

#   This is the code to run Text functions...
def mainTextCode(code):
    global thread1
    result_container = [None, None]
    thread1 = threading.Thread(target=text_thread_run, args=(code, result_container),daemon=True)
    thread1.start()
    timeout = 15 # change timeout settings in seconds here...
    thread1_start_time = time.time()
    while thread1.is_alive():
        if time.time() - thread1_start_time > timeout:
            stop_thread(thread1)
            raise Exception(f"TimeoutError: Python code execution exceeded the timeout limit : {timeout}.")
        time.sleep(1)
    if result_container[1]:  # If there's an error
        raise result_container[1]
    elif result_container[0] is None:
        raise Exception("UnknownError: Python code did not return a result or raise an exception.")
    return result_container[0]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant