Black Lives Matter. Please consider donating to Campaign Zero's mission of ending police violence in America.
https://www.joincampaignzero.org

Solution for downloading a zipped folder/directory?

I’m looking to create a link to be able to download a zipped folder that already exists in the same directory as the dash application. I’ve seen solutions to download raw csv data (Download raw data), but am not quite sure how to change this to be able to download a .zip folder.

Here is the basic framework I’m trying to modify (from link above):

html.A(
        'Download Zipped Folder',
        id='download-link',
        download="folder.zip",
        href="",
        target="_blank"
    )

@app.callback(
    dash.dependencies.Output('download-link', 'href'),
    [dash.dependencies.Input('field-dropdown', 'value')])
def update_download_link(filter_value):
    dff = filter_data(filter_value)
    csv_string = dff.to_csv(index=False, encoding='utf-8')
    csv_string = "data:text/csv;charset=utf-8," + urllib.quote(csv_string)
    return csv_string

Thanks in advance!

What does the zip contain? One/many CSV files that you want to use in a dataframe?

The zip contains a variety of different files (.txt, .bedgraph, .bed) for the user to download after performing data analysis with the Dash App.

See Allowing users to download CSV on click for a more general solution for serving larger files and https://stackoverflow.com/a/27337047/4142536 to adapt that example to a zip file.

If you get this to work, please post your solution here too!

I was able to get this to work:

from flask import send_file
app = dash.Dash()

app.layout = html.Div([

	html.A(
        'Download Zip',
        id='download-zip',
        download = "example.zip",
        href="",
        target="_blank",
        n_clicks = 0
	    )
	])

@app.callback(
    Output('download-zip', 'href'),
    [Input('download-zip', 'n_clicks')])

def generate_report_url(n_clicks):

	return '/dash/urldownload'

@app.server.route('/dash/urldownload')

def generate_report_url():

	return send_file('example.zip', attachment_filename = 'example.zip', as_attachment = True)

if __name__ == '__main__':
	app.run_server(debug = True)
2 Likes

I am dealing with similar problem.
Did someone find solution to this problem with the original framework (the one that in the post)?
Alternative, did someone try using the solution suggested above? because it doesn’t work for me.

Anyone? sorry for the bouncing

Here is the way I download my zip of excel files, with the zip file being created on-the-fly. In this example, the individual excel files reside in my AWS instance.

NOTE: This entire process must be achieved within 30 seconds if hosting on Heroku due to the web process time limit; unless you implement this asynchronously.

The href assoc. with the button to invoke the download looks similar to the following:

http://127.0.0.1:8050/download_zip/?filename=2020_06May_13May_Excel.zip&file_0=2020/May/06May_13May/20-05-06_Data.xlsx&file_1=2020/May/06May_13May/20-05-06_Data2.xlsx&file_2=2020/May/06May_13May/20-05-06_Data3.xlsx&num_files=3

Here is the code that handles the downloading of these files from my AWS instance, creating a zip to contain them, and then downloading to the user.

import flask
import io
import zipfile


@app.server.route('/download_zip/')
def download_zip_file():
    # Name of the zip file
    filename = flask.request.args.get('filename')
    # Num of files to process
    num_files = flask.request.args.get('num_files')

    # All files passed in via url string are of the format file_<filename>
    files = []
    for file in range(int(num_files)):
        files.append(flask.request.args.get('file_' + str(file)))

    # Loop thru files and add to zip
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED, False) as zip_file:
        for file in files:
            # aws.download is an internal function to download the file from my AWS S3 instance
            file_obj = aws.download(file)
            zip_file.writestr(file.split('/')[-1], file_obj['Body'].read())

    return flask.Response(
        zip_buffer.getvalue(),
        mimetype='binary/octet-stream',
        headers={"Content-Disposition": "attachment;filename={}".format(filename)}
    )

Here is the aws.download() function:

import boto3

# Download a file from the bucket
def download(key):
    # Create connection to the bucket
    s3_resource = boto3.resource('s3')
    bucket = s3_resource.Bucket(S3_BUCKET)

    # Return file object
    return bucket.Object(key).get()

Hope this helps and you can adapt to your needs.