From 8919edf84513ff72772a874c524536e8ab6177d3 Mon Sep 17 00:00:00 2001 From: Ryan Waldheim Date: Tue, 21 Jan 2025 17:22:08 -0500 Subject: [PATCH 1/3] is a Valid Prop --- dash/_callback.py | 12 +------- dash/_no_update.py | 9 ++++++ dash/_validate.py | 10 +++++-- test.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 dash/_no_update.py create mode 100644 test.py diff --git a/dash/_callback.py b/dash/_callback.py index 071c209dec..0ed452d773 100644 --- a/dash/_callback.py +++ b/dash/_callback.py @@ -37,23 +37,13 @@ from . import _validate from .long_callback.managers import BaseLongCallbackManager from ._callback_context import context_value +from ._no_update import NoUpdate def _invoke_callback(func, *args, **kwargs): # used to mark the frame for the debugger return func(*args, **kwargs) # %% callback invoked %% -class NoUpdate: - def to_plotly_json(self): # pylint: disable=no-self-use - return {"_dash_no_update": "_dash_no_update"} - - @staticmethod - def is_no_update(obj): - return isinstance(obj, NoUpdate) or ( - isinstance(obj, dict) and obj == {"_dash_no_update": "_dash_no_update"} - ) - - GLOBAL_CALLBACK_LIST = [] GLOBAL_CALLBACK_MAP = {} GLOBAL_INLINE_SCRIPTS = [] diff --git a/dash/_no_update.py b/dash/_no_update.py new file mode 100644 index 0000000000..b86004de72 --- /dev/null +++ b/dash/_no_update.py @@ -0,0 +1,9 @@ +class NoUpdate: + def to_plotly_json(self): # pylint: disable=no-self-use + return {"_dash_no_update": "_dash_no_update"} + + @staticmethod + def is_no_update(obj): + return isinstance(obj, NoUpdate) or ( + isinstance(obj, dict) and obj == {"_dash_no_update": "_dash_no_update"} + ) diff --git a/dash/_validate.py b/dash/_validate.py index a26fd0f73b..f3cc4ca2eb 100644 --- a/dash/_validate.py +++ b/dash/_validate.py @@ -6,6 +6,7 @@ import flask from ._grouping import grouping_len, map_grouping +from ._no_update import NoUpdate from .development.base_component import Component from . import exceptions from ._utils import ( @@ -211,8 +212,10 @@ def validate_multi_return(output_lists, output_values, callback_id): def fail_callback_output(output_value, output): - valid_children = (str, int, float, type(None), Component) - valid_props = (str, int, float, type(None), tuple, MutableSequence) + valid_children = (str, int, float, type(None), Component, NoUpdate) + valid_props = (str, int, float, type(None), tuple, MutableSequence, NoUpdate) + + print("================================") def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False): bad_type = type(bad_val).__name__ @@ -261,6 +264,7 @@ def _valid_prop(val): return isinstance(val, valid_props) def _can_serialize(val): + print("checking ability to serialize") if not (_valid_child(val) or _valid_prop(val)): return False try: @@ -272,6 +276,7 @@ def _can_serialize(val): def _validate_value(val, index=None): # val is a Component if isinstance(val, Component): + print("Is Component") unserializable_items = [] # pylint: disable=protected-access for p, j in val._traverse_with_paths(): @@ -332,6 +337,7 @@ def _validate_value(val, index=None): if isinstance(output_value, list): for i, val in enumerate(output_value): + print(val) _validate_value(val, index=i) else: _validate_value(output_value) diff --git a/test.py b/test.py new file mode 100644 index 0000000000..ccd09f9de9 --- /dev/null +++ b/test.py @@ -0,0 +1,69 @@ +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) \ No newline at end of file From b99ff5f5cebe9e2ef974cf7aac8860eae7afdb3e Mon Sep 17 00:00:00 2001 From: Ryan Waldheim Date: Tue, 21 Jan 2025 17:43:28 -0500 Subject: [PATCH 2/3] remove prints --- dash/_validate.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dash/_validate.py b/dash/_validate.py index f3cc4ca2eb..7d32fbb5bd 100644 --- a/dash/_validate.py +++ b/dash/_validate.py @@ -215,8 +215,6 @@ def fail_callback_output(output_value, output): valid_children = (str, int, float, type(None), Component, NoUpdate) valid_props = (str, int, float, type(None), tuple, MutableSequence, NoUpdate) - print("================================") - def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False): bad_type = type(bad_val).__name__ outer_id = f"(id={outer_val.id:s})" if getattr(outer_val, "id", False) else "" @@ -264,7 +262,6 @@ def _valid_prop(val): return isinstance(val, valid_props) def _can_serialize(val): - print("checking ability to serialize") if not (_valid_child(val) or _valid_prop(val)): return False try: @@ -276,7 +273,6 @@ def _can_serialize(val): def _validate_value(val, index=None): # val is a Component if isinstance(val, Component): - print("Is Component") unserializable_items = [] # pylint: disable=protected-access for p, j in val._traverse_with_paths(): @@ -337,7 +333,6 @@ def _validate_value(val, index=None): if isinstance(output_value, list): for i, val in enumerate(output_value): - print(val) _validate_value(val, index=i) else: _validate_value(output_value) From 8ec605cac3f69f14bed28c2bc5822a37d3ebd56c Mon Sep 17 00:00:00 2001 From: Ryan Waldheim Date: Tue, 21 Jan 2025 17:51:14 -0500 Subject: [PATCH 3/3] remove test.py --- test.py | 69 --------------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index ccd09f9de9..0000000000 --- a/test.py +++ /dev/null @@ -1,69 +0,0 @@ -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) \ No newline at end of file