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

implementing callback_fallback #2829

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

Conversation

BSd3v
Copy link
Contributor

@BSd3v BSd3v commented Apr 5, 2024

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

…r universal error handling of callbacks.
@ndrezn
Copy link
Member

ndrezn commented Apr 8, 2024

@ndrezn
Copy link
Member

ndrezn commented Apr 15, 2024

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

@BSd3v
Copy link
Contributor Author

BSd3v commented Apr 29, 2024

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.

Copy link
Member

@ndrezn ndrezn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this pattern is a bit confusing. We're passing a decorator as an argument: With this API I would expect to pass a function as an argument. I guess in general I feel like there's still some API design to be done here.

What is the ideal way to pass in an error fallback for a Dash callback? It's not immediately obvious to me, but I think we can definitely simplify the pattern from having to pass a decorator. @T4rk1n might have some other comments... but we might want to spend some time in an issue ticket discussing this feature more broadly before starting implementation?

@BSd3v
Copy link
Contributor Author

BSd3v commented Apr 29, 2024

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?

@ndrezn
Copy link
Member

ndrezn commented Apr 29, 2024

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.

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.

[Feature Request] Callback Errors on production environments with Debug=False
3 participants