emilhe / dash-extensions

The dash-extensions package is a collection of utility functions, syntax extensions, and Dash components that aim to improve the Dash development experience
https://www.dash-extensions.com/
MIT License
417 stars 59 forks source link

DashLogger breaks a callback that returns a dict #202

Open larryhengl opened 2 years ago

larryhengl commented 2 years ago

Hi, I'm outputting a dict to a store from a callback that has a DashLogger and the Notification object is getting merged into the dict. If I return a string or a list of strings it works fine, but not for a dict:

@app.callback(
  Output("store-notification", "data"),
  Input("stop-btn", "n_clicks"),
  log = True
)
def stop_btn(n_clicks, dash_logger: DashLogger):
  if not n_clicks:
      raise PreventUpdate()
  dash_logger.warning("stop btn was hit")
  return {"status":"new","msg":"new upload"}

Dash error:

dash._grouping.SchemaTypeValidationError: Schema: [<Output `{"id":"store-notification","idx":0,"prop":"data"}.data`>, <Output `notifications_provider.children`>]
Path: ()
Expected type: (<class 'tuple'>, <class 'list'>)
Received value of type <class 'dict'>:
{'status': 'new', 'msg': 'new upload', 1: [Notification(id='1d13fc57-fe59-440a-b5c2-72031bb62219', action='show', autoClose=False, color='yellow', message='stop btn was hit', title='Warning')]}

Returning the dict in a list does not error from the callback but breaks other store dependencies that are expecting a dict. Also tried returning a tuple without success.

Any workaround ideas?

Many thanks!

emilhe commented 2 years ago

That sounds like a bug. Could you post a complete MWE demonstrating the issue? Then I'll take a look at a bugfix

larryhengl commented 2 years ago

MWE with error. I included some working variants too. Thanks!

from dash_extensions.enrich import Output, Input, html, dcc, State, DashProxy, LogTransform, DashLogger, MultiplexerTransform
from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc

app = DashProxy(__name__,
          title="Test",
          update_title=None,
          url_base_pathname="/",
          transforms=[
            MultiplexerTransform(),
            LogTransform()
          ]
      )

app.layout = dmc.MantineProvider(
  withGlobalStyles = True,
  children = html.Div(
    [
      dcc.Store(id = "store", data = None),
      dmc.Button("Push Me", id = "btn", size = "lg"),
      html.Div(id = "billboard")
    ]
  )
)

# [Working]
# cb to save DICT to store, without notification
# @app.callback(
#   Output("store","data"),
#   Input("btn","n_clicks"),
#   prevent_initial_call = True
# )
# def cb1(n_clicks):
#   if n_clicks is not None:
#     return {"status":"click","msg":f"clicked {n_clicks} time{'s' if n_clicks > 0 else ''}"}
#   else:
#     return None

# [Working]
# cb to save TEXT to store, trigger notification
# @app.callback(
#   Output("store","data"),
#   Input("btn","n_clicks"),
#   log = True,
#   prevent_initial_call = True
# )
# def cb1(n_clicks, dash_logger: DashLogger):
#   dash_logger.warning("btn was clicked",autoClose = 2000)
#   if not n_clicks:
#     raise PreventUpdate()
#   else:
#     return f"clicked {n_clicks} time{'s' if n_clicks > 0 else ''}"

# [Not Working]
# cb to save DICT to store, trigger notification
@app.callback(
  Output("store","data"),
  Input("btn","n_clicks"),
  log = True,
  prevent_initial_call = True
)
def cb1(n_clicks, dash_logger: DashLogger):
  if not n_clicks:
    raise PreventUpdate()
  dash_logger.warning("btn was clicked",autoClose = 2000)
  if n_clicks is not None:
    return {"status":"click","msg":f"clicked {n_clicks} time{'s' if n_clicks > 0 else ''}"}
  else:
    return None

# cb to use store in ui
@app.callback(
  Output("billboard","children"),
  Input("store","data"),
  prevent_initial_call = True
)
def cb2(data):
  if data:
    if isinstance(data, str):
      msg = data
    else:
      msg = data["msg"]
    return dmc.Alert(
      id = "alert",
      children = [msg],
      title = "Clicked!",
      color = "blue",
      withCloseButton = True
    )
  else:
    return "no clicks"

if __name__ == "__main__":
  app.run_server(debug = True)

# http://localhost:8050/
connorferster commented 9 months ago

I have run into this, too. It happens on "dict-like" return values like dcc.send_file.

I am loving the LogTransform with the dash mantine components. Thanks for building it. Being able to pass user-notifications makes my (rather large) internal corporate app just much more robust and user-friendly.

Any updates on this issue?