python-eel / Eel

A little Python library for making simple Electron-like HTML/JS GUI apps
MIT License
6.47k stars 589 forks source link

How to handle my own exceptions? #262

Closed Thoufak closed 4 years ago

Thoufak commented 4 years ago

Describe the problem I want to be able to handle erros raised inside my python functions which are exposed with eel.expose. However, it seems that all the exceptions are handled somewhere inside the library.

Code snippet(s)

import eel

# This gets called from JS
@eel.expose
def will_raise():
    ...
    raise Exception()

try:
    eel.start("index.html")
except:
    # This is never called
    ...

Console output:

Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 766, in gevent._greenlet.Greenlet.run
  File "/Users/ss/.pyenv/versions/3.6.5/lib/python3.6/site-packages/eel/__init__.py", line 257, in _process_message
    return_val = _exposed_functions[message['name']](*message['args'])
  File "/Users/ss/Desktop/DekaImageObfuscator/main.py", line 56, in wrapper_decorator
    value = func(*args, **kwargs)
  File "/Users/ss/Desktop/DekaImageObfuscator/main.py", line 290, in progress_screen_did_load
    obfuscated_image = obfuscator.obfuscate(chosen_image_path)
  File "/Users/ss/Desktop/DekaImageObfuscator/image_obfuscation.py", line 95, in obfuscate
    image = PilImage.open(image_or_path)
  File "/Users/ss/.pyenv/versions/3.6.5/lib/python3.6/site-packages/PIL/Image.py", line 2862, in open
    "cannot identify image file %r" % (filename if filename else fp)
PIL.UnidentifiedImageError: cannot identify image file '/Users/ss/Desktop/fake image.png'
2020-03-27T14:31:34Z <Greenlet at 0x10a427048: _process_message({'call': 1.7448135464592813, 'name': 'progress_scr, <geventwebsocket.websocket.WebSocket object at 0x1)> failed with UnidentifiedImageError

Desktop:

ChrisKnott commented 4 years ago

There's not really any way to do this in the current version, you have to catch it yourself inside the exposed function.

In future it would be good to come up with a better solution for Exceptions. It's not an easy problem to solve, due to the threading you would have to pass the exception across threads and re-raise it. It's doubly complicated because the logical call stack actually starts in JS.

RahulBadenkal commented 4 years ago

One laborous way (which i usually do) would be what @ChrisKnott suggests. Just mimic how exceptions are handled in an API based model. That is make sure after every exposed function call (API call), there is always a return value in json format. (whether any exception occured or not)

# Not exposed to eel
def actual_work_here(arg):
    # Do the actual computation here
    return data

@eel.expose
def api(arg):
    retval = {'result': True, errmsg: 'Success', 'errcode': 200, 'data': {}}
    try:
       # TODO: Validate Inputs before calling the actual_work_here function.
       x = actual_work_here(arg)
       # Set Return Val
       retva['data'] = {'someParam': x}
    except Exception as e:
       retval['result'] = False
       retval['errcode'] = 500  # Set this according to http standards or use your own custom defined codes 
       retval['errmsg'] = str(e)
    finally:
    return retval

def start_app(arg1):
    # Start the server
    try:
        BASEDIR = os.path.dirname(os.path.abspath(__file__))
        web_dir = BASEDIR
        start_html_page = 'index.html'
        eel.init(web_dir)
        logging.info("App Started")

        eel.start(start_html_page, port=0)
    except Exception as e:
        # TODO: Bring out a tkinter popup saying, server could not be laucnhed
        err_msg = 'Could not launch a local server'
        logging.error('{}\n{}'.format(err_msg, e.args))
        logging.info('Closing App')

Obviously you can generalise a lot here. Write a handle exception function and decorate all your exposed functions with that.

P.S. @ChrisKnott I don't think decorators work in eel exposed functions. I think it would be possible to make eel execute the wrapper functions without running into any threading issues. That would make it way easier to handle exceptions in one centralised place.

Thoufak commented 4 years ago

Thank you very much! I'll look into the provided example.

KaviMani09 commented 4 months ago

[Running] python -u "e:\New folder\jarvis\main.py" listening.... recognizing user said:play JavaScript tutorial in YouTube play javascript tutorial in youtube Traceback (most recent call last): File "C:\Users\MANI\AppData\Roaming\Python\Python312\site-packages\eel__init__.py", line 318, in _process_message return_val = _exposed_functionsmessage['name'] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "e:\New folder\jarvis\engine\command.py", line 55, in allCommands PlayYoutube(query) File "e:\New folder\jarvis\engine\features.py", line 32, in PlayYoutube speak("Playing "+search_term+" on YouTube")


TypeError: can only concatenate str (not "NoneType") to str