Pattern for avoiding circular dependency in Dash callbacks

I’m having trouble converting a fairly simple React concept into Dash – I need to set the state before AND after a long running task. I have a dashboard where a button initiates “long running task”, the completion of which populates the dashboard. I would like to display a loading message in the meantime. In React I would set an isLoading property and update the state accordingly upon completion of “long running task”. In dash, I am attempting to replicate this with a hidden “status” div that toggles between “ready/loading”. The flow I would like is:

  • User clicks button to start process

  • Status div changes to “loading”

  • The loading screen is listening to the Status div and updates to be visible

  • The long running process begins

  • After the process finishes, the status div is updated to ready

  • Loading animation becomes invisible

  • Resulting dashboard becomes visible

My callback structure:

  • Status div listens to button click (sets status to “loading”) and graph data to set to know when completed (set “ready”)
  • graph data listens to status div to know when to begin the calculation
  • loading screen listens to status div to know when to display and to graph data to know when to stop

However, this causes a circular dependency error because button → status → graph → status → graph

Is there a different pattern that can accomplish this in Dash?

As a quick workaround, your global dash app container get the CSS class ._dash-loading-callback when updating its content. You might have look at 📣 Dash Loading States to see if you could use this.

Otherwise, if I understand your app correctly, I would create following callbacks:

  1. Set_loading_status:
  • Input: button, n_clicks
  • Output: status_div, children
  1. Start_long_process:
  • Input: button, n_clicks
  • Output: graph, figure
  1. Update_loading_status:
  • Input: graph, figure
  • Output: status_div, children

This way, 1 and 2 are fired together, and 3 is fired when 2 is completed. You would put both “loading” and the animation in status_div (hard to be more explicit without some proper MWE).
(This might be a bit more difficult as 1 and 3 have the same output — and I think I remember you cannot have two callbacks with the same output (for the moment). If it’s actually the case, you should create one global callback with both 1 & 3 triggers, retrieve which trigger fired the callback, and set output accordingly.)

You can show/hide the graph using CSS (display: none or visibility: hidden, depending on what you want to achieve). You would then need a 1bis and 3bis callbacks that have the same inputs, and use graph, style (or — preferably — graph, className if you use proper CSS).

You pretty much have described what I have currently implemented. As you mentioned, 1 + 3 have to be combined. An issue I have found with having 1 and 2 is that I am unable (I think) to control the order that the callbacks fire, so what I encounter is that once the start button is clicked the loading screen does not appear until after the long running process has completed. This can equivalently be seen by changing the style of some other element onclick with the same button that triggers the long running task.

I get it now. IMHO, what you are looking for is: “how to define the fire order for callbacks having the same trigger” (I assumed they would have been fired simultaneously).

I’ve looked a bit around and it seems that if two callbacks are to be triggered “simultaneously”, there is no way to define which will actually be fired first. (Note that more experienced user might have more insights on this.)
To get a clearer view of the callback, you can have a look at Dash callback chain (kudos to @OliverBrace). Yet this doesn’t solve your issue.

Note: this is a WIP: I just realize we’re facing the same issue (two callbacks fired at the same time)

Here is a logic way that could work:

You have to create a hidden placeholder_div. When you click on the button, you will update the content of this placeholder_div. You make sure you actually change it by reading its content using a State

1 Like

This is not working for me. see Multiple Callbacks for single inputs - not all are firing

1 Like

In case it is useful for somebody, I managed to workaround circular dependencies to implement a progress bar during the processing of the data. In my case, the final data is a table inside a Div, enclosing_div. My solution is based on displaying the progress bar in the same Div as the final table: this removes the need to delete/hide it when it is no longer needed. When ready and displayed the table replaces it. The callback structure I use to achieve this is the following:

  • In the layout I add 2 dcc.Store, pb_indicator and pb_saved_indicator, with their data property initialized to 0.

  • The main callback, doing the real work, is fired by a change of pb_indicator value, one of its Input or one of the other inputs that depend on your application. It also receives as a State pb_saved_indicator. This callback is fired twice when data processing is required. Each time the value of pb_indicator is comparated with pb_saved_indicator.

    • If they are equal, it means it is the first execution and the progress bar is displayed in enclosing_div, as well as a dcc.Interval configurer to run once (max_intervals=1). This dcc.Interval will be in charge of triggering the current callback a second time by incrementing pb_indicator. This is done by returning something like
                  component = html.Div([
                      create_progress_bar,       # A function you need to write to initialize the main progress bar is the usual way
                      dcc.Interval(id='trigger-interval', n_intervals=0, max_intervals=1, interval=500)
                  ])
    
    • If pb_indicator value is greater than pb_saved_indicator, the data processing is started and at the end of it, pb_saved_indicator is updated with pb_indicator.
  • The callback for the dcc_interval that writes in pb_indicator the value of pb_saved_indicator + 1.

Michel

1 Like