Handling UI callback exceptions with decorators #289
Replies: 2 comments 4 replies
-
I guess that's exactly what you need to do to build a decorator for all supported event handlers. In the end it's "only" 13 lines of code. Alternatively you could simplify the decorator by either enforcing all event handlers in you project to accept event arguments, whether they need them or not, or by splitting it into different decorators, e.g. for sync and for async functions. But I think your approach is easier to use. |
Beta Was this translation helpful? Give feedback.
-
Thanks @falkoschindler for the info. I wanted to make sure I did not miss any other shortcut about handling callback exceptions. Here is my latest version below for custom exception handlers. import asyncio
import inspect
import traceback
import nicegui.events
from nicegui import ui
def _make_ui_except_decorator(func, except_handler):
"""Decorator maker to catch callback exceptions with custom handler."""
# nicegui appends a ClickEventArguments to the callback, but only if it detects
# that the callee (our wrapper) accepts parameters (which is not the case of func)
func_params = len(inspect.signature(func).parameters) # How many paramaters the function expects
async def wrapper(*args, **kwargs): # Create wrapper to catch exceptions
try:
if len(args) > func_params and isinstance(args[-1], nicegui.events.ClickEventArguments):
args = args[:-1] # Remove extra argument to avoid TypeError
if inspect.iscoroutinefunction(func): # Check if was called async
return await func(*args, **kwargs)
return func(*args, **kwargs) # Else normal call
except Exception as e:
if except_handler:
except_handler(e) # Call custom exception handler
return wrapper
def ui_except_notify(func):
"""A UI callback decorator to catch exceptions and ui.notify them."""
def except_handler(e):
ui.notify(f"Wrapper Catched Error: {type(e)}: {e}")
return _make_ui_except_decorator(func, except_handler)
def ui_except_dialog(func):
"""A UI callback decorator to catch exceptions and open a dialog with traceback."""
def except_handler(e):
with ui.dialog().props('position=bottom') as dialog, ui.card().classes('bg-warning'):
with ui.card_section().classes('pb-0 row w-full justify-between'):
ui.label(f"Callback Exception: {e}").classes('text-h6')
ui.button(on_click=dialog.close).props('flat rounded dense icon=close').classes('w-min')
with ui.card_section().classes('pt-0 w-full overflow-x-auto'):
ui.html(f"<pre>{traceback.format_exc()}</pre>").classes('text-sm')
dialog.open()
return _make_ui_except_decorator(func, except_handler)
@ui_except_dialog
async def test_a():
ui.label("Without args")
await asyncio.sleep(1)
1/0 # Test a divison by zero exception
@ui_except_dialog
async def test_b(event):
ui.label("With args")
await asyncio.sleep(1)
ui.label(f"Sender:{event.sender}")
ui.label(f"Client:{event.client}")
1/0 # Test a divison by zero exception
@ui_except_dialog
def test_c():
ui.label("Without args, no async")
1/0 # Test a divison by zero exception
ui.label('Testing')
ui.button("Div by 0, no args", on_click=test_a)
ui.button("Div by 0, with args", on_click=test_b)
ui.button("Div by 0, no args, no async", on_click=test_c)
ui.run() As a suggestion, the nicegui.ui package could expose some default exception decorators for convenience, such as @ui.except_notify and @ui.except_dialog from the example above. ui.set_callback_except_handler(lambda e: ui.notify(f"Wrapper Catched Error: {type(e)}: {e}"))
ui.set_callback_except_handler(None) # No handler, silent as before Let me know what you think. |
Beta Was this translation helpful? Give feedback.
-
This is what I had to do to handle exceptions using wrappers. Any better idea?
Beta Was this translation helpful? Give feedback.
All reactions