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

NoUpdate is a Valid Prop #3127

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

Conversation

2Ryan09
Copy link

@2Ryan09 2Ryan09 commented Jan 21, 2025

Resolves #3090.

I was able to reproduce the error using a minimal example inferred by code provided by the issue author:

from types import SimpleNamespace

import dash_bootstrap_components as dbc
import plotly.graph_objects as go
from dash import html, Dash, dcc, Input, Output, no_update, callback, dash_table


app = Dash()

app.layout = html.Div(
        [
            dbc.Alert(id="alert", is_open=False, duration=4000),
            dcc.DatePickerRange(
                id="date_picker",
                start_date="2021-01-01",
                end_date="2021-01-31",
            ),
            dcc.Graph(id="figcontainer"),
            dash_table.DataTable(id="table"),
        ]
    )


@callback(
    Output(component_id="figcontainer", component_property="figure"),
    Output(component_id="table", component_property="data"),
    Output(component_id="alert", component_property="is_open"),
    Output(component_id="alert", component_property="children"),
    Input(component_id="date_picker", component_property="start_date"),
    Input(component_id="date_picker", component_property="end_date"),
)
def update_graph(start, end):
    df = get_bookings_in_interval(start, end)
    # if there is no data, keep previous states and use alert
    if type(df) is AssertionError:
        return no_update, no_update, True, df

    fig = go.Figure()

    return (
        fig.to_dict(),
        {},
        no_update,
        no_update,
    )

mock_response = SimpleNamespace(
    status_code=404,
)

# either returns a df or an AssertionError
def get_bookings_in_interval(start, end):
    df = None
    try:
        data = mock_response
        assert data.status_code == 200, "Failed to fetch bookings"
        parsed_data = dict(data.json())
        assert len(parsed_data["bookings"]) > 0, "No items in Response"
        # do something

    except AssertionError as e:
        print(e)
        return e

    return data


if __name__ == '__main__':
    app.run(debug=True)

raises

Traceback (most recent call last):
  File "/Users/raw/dash/dash/_callback.py", line 562, in add_context
    jsonResponse = to_json(response)
                   ^^^^^^^^^^^^^^^^^
  File "/Users/raw/dash/dash/_utils.py", line 26, in to_json
    return to_json_plotly(value)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raw/miniconda3/envs/dash/lib/python3.12/site-packages/plotly/io/_json.py", line 171, in to_json_plotly
    return _safe(orjson.dumps(cleaned, option=opts).decode("utf8"), _swap_orjson)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Type is not JSON serializable: AssertionError

During handling of the above exception, another exception occurred:

dash.exceptions.InvalidCallbackReturnValue: The callback for `[<Output `figcontainer.figure`>, <Output `table.data`>, <Output `alert.is_open`>, <Output `alert.children`>]`
                returned a value having type `NoUpdate`     <--- Incorrectly attributes the error to `NoUpdate`
                which is not JSON serializable.


The value in question is either the only value returned,
or is in the top level of the returned list,

                and has string representation
                `<dash._no_update.NoUpdate object at 0x10a3da870>`

                In general, Dash properties can only be
                dash components, strings, dictionaries, numbers, None,
                or lists of those.

The apparent error was NoUpdate was being blamed for not being JSON serializable, even though the AssertionError output was to blame. The root cause ended up being NoUpdate was not deemed to be a valid prop by both valid_props and valid_children within _validate.fail_callback_output.

If the error-causing output was placed before the NoUpdate outputs in the return, the correct error message would have been displayed.

E.g.

# ... existing code
    if type(df) is AssertionError:
        return df, no_update, no_update, True # df is `AssertionError`

raises

Traceback (most recent call last):
  File "/Users/raw/dash/dash/_callback.py", line 562, in add_context
    jsonResponse = to_json(response)
                   ^^^^^^^^^^^^^^^^^
  File "/Users/raw/dash/dash/_utils.py", line 26, in to_json
    return to_json_plotly(value)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raw/miniconda3/envs/dash/lib/python3.12/site-packages/plotly/io/_json.py", line 171, in to_json_plotly
    return _safe(orjson.dumps(cleaned, option=opts).decode("utf8"), _swap_orjson)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Type is not JSON serializable: AssertionError

During handling of the above exception, another exception occurred:

dash.exceptions.InvalidCallbackReturnValue: The callback for `[<Output `figcontainer.figure`>, <Output `table.data`>, <Output `alert.is_open`>, <Output `alert.children`>]`
                returned a value having type `AssertionError`    <------ Here `AssertionError` is being properly blamed
                which is not JSON serializable.


The value in question is either the only value returned,
or is in the top level of the returned list,

                and has string representation
                `Failed to fetch bookings`

                In general, Dash properties can only be
                dash components, strings, dictionaries, numbers, None,
                or lists of those.

This PR adds NoUpdate to valid_props and valid_children, which results in the expected error.

One open question is the meaningful location for the NoUpdate definition. I made a new _no_update module, which seems like overkill, but I did not see a better place and importing _callback into _validate resorts in a circular dependency.

Side note: hello NoUpdate again, my old friend :)

@2Ryan09 2Ryan09 changed the title is a Valid Prop NoUpdate is a Valid Prop Jan 21, 2025
@gvwilson gvwilson added P1 needed for current cycle fix fixes something broken community community contribution labels Jan 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community community contribution fix fixes something broken P1 needed for current cycle
Projects
None yet
Development

Successfully merging this pull request may close these issues.

dash.no_update is not serializable by json is raised if other, not serializiable type is passed as output
3 participants