dash
dash copied to clipboard
Add support for custom error handling in Dash
implementing callback_fallback on Dash and callback to allow for universal error handling of callbacks.
This manipulates a new variable in _callback GLOBAL_CALLBACK_FALLBACK in order to pass the configuration to all other callbacks from the Dash() registration
see here for a forum post demonstrating this functionality: https://community.plotly.com/t/error-handling-for-callbacks-and-layouts/83586/2
Related: https://community.plotly.com/t/error-handling-for-callbacks-and-layouts/83586/2
@BSd3v could you add an example of using this new callback_fallback functionality as implemented here to get a sense of use case/how to document?
Here is an example app:
from dash import *
from dash._utils import to_json
import traceback
from dash import ctx
from dash.exceptions import PreventUpdate
import json
def callback_fallback(output=None):
error_message = "error in callback"
def decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if str(e) == "":
raise PreventUpdate
alertError(f"{error_message}", f"{output}\n {traceback.format_exc()}")
resp = {
"app_notification": {
"children": json.loads(
to_json(
notification(
"error", "there was an issue, IT has been notified", ctx
)
)
)
}
}
return json.dumps({"multi": True, "response": resp})
return wrapper
return decorator
def callback_fallback2(output=None):
error_message = "error in callback"
def decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if str(e) == "":
raise PreventUpdate
alertError(f"{error_message}", f"{output}\n {traceback.format_exc()}")
resp = {
"app_notification": {
"children": json.loads(
to_json(
notification(
"error", "I'm sorry Dave, I'm afraid I can't do that", ctx
)
)
)
}
}
return json.dumps({"multi": True, "response": resp})
return wrapper
return decorator
app = Dash(
__name__,
callback_fallback=callback_fallback,
)
def notification(type, msg, ctx=None):
if type == 'error':
return html.Div(
children=msg + (f' - {ctx.triggered[0]["value"]}' if ctx else ''),
style={'color': 'red'}
)
return ''
def alertError(subject, message):
print(subject)
print(message)
pass
@callback(
Output("children", "children"),
Input("click", "n_clicks"),
State("testChecked", "value"),
prevent_initial_call=True,
)
def partialFailingCall(n, c):
if c:
return rawr
return f"I ran properly - {n}"
@callback(
Output("children2", "children"),
Input("click2", "n_clicks"),
State("testChecked2", "value"),
callback_fallback=callback_fallback2,
prevent_initial_call=True,
)
def partialFailingCall(n, c):
if not c:
return rawr
return f"I ran properly - {n}"
app.layout = html.Div(
children=[html.Div(id="app_notification"),
html.Div([
html.Div("When checked, the callback will fail"),
html.Button("test callback", id="click"),
dcc.Checklist([True], id="testChecked"),
html.Div(id="children")
]
),
html.Div([
html.Div("When not checked, the callback will fail, callback handler is different"),
html.Button("test callback", id="click2"),
dcc.Checklist([True], id="testChecked2"),
html.Div(id="children2")
]
)
]
)
app.run(debug=True)
Notice that in the second callback I am overriding the default callback_fallback in the callback definition.
alertError(f"{error_message}", f"{output}\n {traceback.format_exc()}") resp = { "app_notification": { "children": json.loads( to_json( notification( "error", "I'm sorry Dave, I'm afraid I can't do that", ctx ) ) ) } } return json.dumps({"multi": True, "response": resp})
So just pass something like this instead?
Maybe, but I'd want to get to something even simpler to use. @T4rk1n to open a ticket describing the feature and we can hone in on a spec from there I think makes the most sense.
Here's another use-case for this feature from the forum: https://community.plotly.com/t/errors-showing-up-when-run-locally-but-not-when-deployed-to-gcp/85311/2
Closed with this PR: #2903