`NoUpdate` is a Valid Prop
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 :)
I can confirm this issue is still present in Dash 3.0.4.
Are there any plans to address community issues/pull requests in this repo?
@2Ryan09 we're working through issues when we can, but we are a small team with a large backlog - I apologize for not being able to move faster.