Announcing Dash Bio 1.0.0 🎉 : a one-stop-shop for bioinformatics and drug development visualizations.

Problem with dcc.Download and dcc.send_file

Hi,

I have a complicated problem so a bit of context seems appropriate.

I am creating an app that will allow the scientific community to have access to the Self-Consistent Atmospheric Retrieval framework for ExoplaneTs (Scarlet), so I have created a Dash code that allows me to integrated the SCARLET framework into a web application. So the app is essentially a form containing atmospheric parameters that has to be filled by the user. Once filled, the user click on a submit button and the code retrieved an atmospheric model and shows graph of transit transmission spectrum. The app also allows to download the .csv document of the retrieval data created by SCARLET after each run.

SCARLET indeed create a folder with retrieval data for each retrieval it runs. This where my problems lies.

I have created a download button that allows for download the .csv data file when the user click on it. the first time the button works without problem, but if I retrieve another atmospheric model during the same user session, the download window pups up without having to interact with the download button. This windows wants me to download the previous retrieval data. Once I canceled those proposal, the user can interact with the download button for accessing the right file.

I have try 6-7 different approach to solve this issue, but it always give me the same results.

Here I present the part of the code that are use to generate layout and callbacks for the download process.

The layout components:

               ...
                html.Div([dbc.Row([dbc.Col([html.Div([dbc.Button("submit", id='submit_button',className="me-2", n_clicks=0)]
                                                                    )])])])
                ]),
                dbc.Container(dbc.Row(dcc.Loading(id='loading-1',type='default',children=html.Div(id='col2',children=[ html.Div([ ]),html.Div(children=[ '','','']),html.Div(children=[ '','','']),html.Div(children=[ '','','']),html.Div(children=[ '','','']),html.Div(children=[ '','','']),
                                                        dbc.Row([ dbc.Col([html.Div(id='dynamic-download container1',children=[    
                                                                                                                              html.Div([ ]),html.Div([ ]),html.Div([ ])

                                                                       ])]),
                                                        dbc.Col([ html.Div(id='dynamic-download container2',children=[
                                                                       html.Div([ ]),html.Div([ ])
                                                                       ])])]),]
                              ))))])]) ) #style={'display':'inline-block'}

The callbacks

@app.callback(
Output(‘col2’,‘children’),
Output(‘dynamic-download container1’, ‘children’),
Output(‘dynamic-download container2’, ‘children’),
Input(‘submit_button’, ‘n_clicks’),
State(‘planet_name’, ‘value’),

State(‘col2’,‘children’),
State(‘dynamic-download container1’,‘children’),
State(‘dynamic-download container2’,‘children’),
prevent_initial_call=True,
suppress_callback_exceptions=True)
def main_program(clicks,Planet_Name,Rs,Ls,Teffs,Rp,Mp,Ap,compositionval,
ratioCO, Me, Pquenching, Hyd, He, CO, H2O, CH4, CO2, N2, Tproeval,
Tint, Text, HDF, Grayc, pc, Hc, cHaz, Mieparams, MieRpart,
MiePatTau1, MieRelScaleHeight, Ndens, BAlbedo, GrAlbedo,
GeoAlbedo, J_unit, Children, Children2,Children3):

try:
    atm.runModel(ModelSetting,Params,
    specsfitRadius=specs,fitRadius=fitRadius,shiftDppm=False,dDppmMax=10,
    runName=RunName,disp2terminal=False)
    
except:
    updatelayouterr=html.Div(children=[dcc.Markdown('''### An error occured
                                                                                                      
        If you're using the none-Gray temperature-pression profile, this message means that the algorithm failed to converge toward a solution'  ''')])
    Children[0]=updatelayouterr
    return Children

else:
    atm.save()
    #atm.saveSpectrum(kernel=None,saveMolNames=atm.AbsMolNames)
    atm.saveSpectrum(saveSettings=[1,0,0,0,0,0],kernel=None,saveMolNames=atm.AbsMolNames)
    
    #fileName = atm.specFile
    fileName=atm.filebase+atm.runName+'_Spectrum_FullRes'+'_dppm'
    #fileName1=atm.filebase+atm.runName+'_struc'
    fileName2=atm.filebase+atm.runName+'_strucAll'
    #fileName3=atm.filebase+atm.runName+'_strucTp'
    
    fileName4=atm.filebase+atm.runName+'_Spectrum'+'_dppm'+'.pdf'
    
    xscale='log';  resPower=200
    fig,ax=atm.plotCombo(resPower=resPower,save=True); plt.close(fig)
    
    fig1,ax1=atm.plotSpectrum(save=True,spectype='dppm',specs=specs,resPower=resPower,xscale=xscale,label='dppm'); plt.close(fig)

    
    plotly_fig = fig_to_uri(fig)
    plotly_fig1 = fig_to_uri(fig1)
    
    
    updatelayoutcol2= dbc.Row([dbc.Col(html.Div([dbc.Card([dbc.CardImg(id = 'cur_plot', src = plotly_fig, style={'display':'inline-block'})],className="mb-4")],style={'display':'inline-block'})),
                             dbc.Col(html.Div([dbc.Card([dbc.CardImg(id = 'cur_plot2', src = plotly_fig1, style={'display':'inline-block'})],className="mb-4")],style={'display':'inline-block'}))])
    updatelayoutcol3= html.Div([dcc.Input(id = {'type':'Invisiblefilename','index':'n_clicks'}, value = fileName+'.csv', type='text')],style={'display':'none'})
    #updatelayoutcol4= html.Div([dcc.Input(id = 'Invisiblefilename1', value = fileName1+'.csv', type='text')],style={'display':'none'})
    updatelayoutcol5= html.Div([dcc.Input(id = {'type':'Invisiblefilename2','index':'n_clicks'}, value = fileName2+'.csv', type='text')],style={'display':'none'})
    #updatelayoutcol6= html.Div([dcc.Input(id = 'Invisiblefilename3', value = fileName3+'.csv', type='text')],style={'display':'none'})
    updatelayoutcol7= html.Div([dcc.Input(id = {'type':'Invisiblefilename4','index':'n_clicks'}, value = fileName4, type='text')],style={'display':'none'})
    Children[0]=updatelayoutcol2
    Children[1]=updatelayoutcol3
    #Children[2]=updatelayoutcol4
    Children[3]=updatelayoutcol5
    #Children[4]=updatelayoutcol6
    Children[5]=updatelayoutcol7
    #Children[6]=updatelayoutcol8
    #return plotly_fig, plotly_fig1, Children, fileName
    Children2[0]=dbc.Button("Download Results", id={'type':"btn_csv",'index':'n_clicks'})
    Children2[1]=dcc.Download(id={'type':"download-csv",'index':'n_clicks'})
    Children2[2]=dcc.Download(id={'type':"download-csv1",'index':'n_clicks'})
    
    Children3[0]=dbc.Button("Download TRansmission Spectrum", id={'type':"btn_pdf",'index':'n_clicks'})
    Children3[1]=dcc.Download(id={'type':"download-pdf",'index':'n_clicks'})
    return Children, Children2, Children3

@app.callback(
Output({‘type’:“download-csv”,‘index’:MATCH}, “data”),
Output({‘type’:“download-csv1”,‘index’:MATCH}, “data”),
Input({‘type’:“btn_csv”,‘index’:MATCH}, “n_clicks”),
State({‘type’:‘Invisiblefilename’,‘index’:MATCH}, ‘value’),
State({‘type’:‘Invisiblefilename2’,‘index’:MATCH}, ‘value’),
prevent_initial_call=True,
suppress_callback_exceptions=True
)
def func_csv(n_clicks, value,value1):
file= os.path.dirname(value)+’/’+os.path.basename(value)
file1= os.path.dirname(value1)+’/’+os.path.basename(value1)
return dcc.send_file(file),dcc.send_file(file1)#,dcc.send_file(file2),dcc.send_file(file3)

Thanks for any help you may provide.

Michaël Lévesque

Hi @Mlevesque,

After a quick look in your app, I suspect that the issue here is that you are rewriting the button with the first callback, which would trigger the second callback (even if n_clicks is None), so this callback gets fired after the first. Note that this doesn’t happen on startup because you have prevent_initial_call=True. I imagine that something like this can fix the issue:

def func_csv(n_clicks, value,value1):
    if not n_clicks:
          raise PreventUpdate # from dash.exceptions import PreventUpdate
    file= os.path.dirname(value)+’/’+os.path.basename(value)
    file1= os.path.dirname(value1)+’/’+os.path.basename(value1)
   return dcc.send_file(file),dcc.send_file(file1)#,dcc.send_file(file2),dcc.send_file(file3)

Please also be mindful that your pattern-matching components all have index="n_clicks", which is a fixed value. This can lead to additional problems down the road (if not, then the pattern-matching might not even be needed…).

Please let me know if this helps!

Thank you for the quick response!

No it didn’t, I have also remove the pattern matching callback, just in case but the problem is still there.

Michaël

1 Like

Ok, that’s too bad…

This issue looks very similar to this thread and a solution was found in this case. I had to dig into my history, but I think you could try to implement his approach. There is a very nice explanation here about what is happening, which in that case has nothing to do with the callback being retriggered.

The problem is solved. I had place the download component in the same children list than my graph, so when the graph updated all the component in the list updated cause the dcc.download to remount the last data it has loaded. Removing the download component from the list solved the issue.

2 Likes