dash icon indicating copy to clipboard operation
dash copied to clipboard

Add support for custom error handling in Dash

Open BSd3v opened this issue 1 year ago • 6 comments
trafficstars

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

BSd3v avatar Apr 05 '24 20:04 BSd3v

Related: https://community.plotly.com/t/error-handling-for-callbacks-and-layouts/83586/2

ndrezn avatar Apr 08 '24 14:04 ndrezn

@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?

ndrezn avatar Apr 15 '24 15:04 ndrezn

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.

BSd3v avatar Apr 29 '24 14:04 BSd3v

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?

BSd3v avatar Apr 29 '24 17:04 BSd3v

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.

ndrezn avatar Apr 29 '24 17:04 ndrezn

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

AnnMarieW avatar Jun 20 '24 16:06 AnnMarieW

Closed with this PR: #2903

BSd3v avatar Jul 11 '24 15:07 BSd3v