Create and download zip file

Hi all,
I would like to create a zip file and download it by using dcc.download. Creating the zip works fine but I’ve no idea how to hand it over to the output of the dcc.downlaod. For the moment the zip is going to be safed at the same point in system where the executed file is stored.

Below is my code:

import dash
from dash.dependencies import Output, Input
import dash_html_components as html
import dash_core_components as dcc

import matplotlib.pyplot as plt
import zipfile
import io

app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Download zip", id="btn_txt"), 
                       dcc.Download(id="download-text")]
                     )

@app.callback(
    Output("download-text", "data"),
    Input("btn_txt", "n_clicks"),
    prevent_initial_call=True,
)
def func(n_clicks):
    zip_file_name = "my_export.zip"
    with zipfile.ZipFile(zip_file_name, mode="w") as zf:
        for i in range(3):
            plt.plot([i, i])
            buf = io.BytesIO()
            plt.savefig(buf)
            plt.close()
            img_name = "fig_{:02d}.png".format(i)
            zf.writestr(img_name, buf.getvalue())

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

Since you have already created the zip file, you can use send_file. Something like,

def func(n_clicks):
    zip_file_name = "my_export.zip"
    with zipfile.ZipFile(zip_file_name, mode="w") as zf:
        for i in range(3):
            plt.plot([i, i])
            buf = io.BytesIO()
            plt.savefig(buf)
            plt.close()
            img_name = "fig_{:02d}.png".format(i)
            zf.writestr(img_name, buf.getvalue())
    dcc.send_file(zip_file_name)

Thanks for your answer.
But is there any way to create the zip just in the app without saving it local and drop it directly to dcc.download? Because I’m not shure, weather I’ve accesse to the lokal storage later.

Yes, that is also possible.

Sounds good! I would be very happy if you or anyone else could give an short example or a hint how to solve it. I’ve ni idea for the moment. Thanks!

Here is a small example of in-memory download of a PDF file using send_bytes. I would imagine that a similar approach would be feasible for a zip file,

Well, I tried to make it out of this thread you postet and I think I’m not far away to achive it. I modified the code that the zip file is created in a stream. But how can I download this stream using dcc.download?

import io
from os import close
import dash
from dash.dependencies import Output, Input
from dash_extensions.snippets import send_bytes
import dash_html_components as html
import dash_core_components as dcc

import matplotlib.pyplot as plt
import zipfile
from io import BytesIO

app = dash.Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Download zip", id="btn_txt"), 
                       dcc.Download(id="download-text")]
                     )

@app.callback(
    Output("download-text", "data"),
    Input("btn_txt", "n_clicks"),
    prevent_initial_call=True,
)
def func(n_clicks):
    archive = BytesIO()
    
    with zipfile.ZipFile(archive, mode="w") as zf:
        for i in range(3):
            plt.plot([i, i])
            buf = io.BytesIO()
            plt.savefig(buf)
            plt.close()
            img_name = "fig_{:02d}.png".format(i)
            zf.writestr(img_name, buf.getvalue())

    return send_bytes(archive, "some_name.zip") # does not work
    
if __name__ == "__main__":
    app.run_server(debug=False)

You are close :wink:

Thanks HConBike and Emil, this worked for me as well. For those wanting to wrap multiple pandas dataframes as individual csv files inside a single zip file for download, you can convert dataframes to bytes using df.to_csv().encode(‘utf-8’) as per this answer on SO. The callback I used then looks like this:

@app.callback(Output("download-text", "data"), Input("btn_txt", "n_clicks"))
def func(n_clicks):
    def write_archive(bytes_io):
        with zipfile.ZipFile(bytes_io, mode="w") as zf:
            for df,fn in zip(df_list,fileNameList):
                df_bytes=df.to_csv(index=False).encode('utf-8')
                fileName = f'{fn}.csv'
                zf.writestr(fileName ,df_bytes)
    return dcc.send_bytes(write_archive, "zipFileName.zip")
1 Like