Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

403
Views
Best way to create a download link for a file in Flask?

In my project, when a user clicks a link, an AJAX request sends the information required to create a CSV. The CSV takes a long time to generate and so I want to be able to include a download link for the generated CSV in the AJAX response. Is this possible?

Most of the answers I've seen return the CSV in the following way:

return Response(
        csv,
        mimetype="text/csv",
        headers={"Content-disposition":
                 "attachment; filename=myplot.csv"})

However, I don't think this is compatible with the AJAX response I'm sending with:

return render_json(200, {'data': params})

Ideally, I'd like to be able to send the download link in the params dict. But I'm also not sure if this is secure. How is this problem typically solved?

over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

I think one solution may the futures library (pip install futures). The first endpoint can queue up the task and then send the file name back, and then another endpoint can be used to retrieve the file. I also included gzip because it might be a good idea if you are sending larger files. I think more robust solutions use Celery or Rabbit MQ or something along those lines. However, this is a simple solution that should accomplish what you are asking for.

from flask import Flask, jsonify, Response
from uuid import uuid4
from concurrent.futures import ThreadPoolExecutor
import time
import os
import gzip

app = Flask(__name__)

# Global variables used by the thread executor, and the thread executor itself
NUM_THREADS = 5
EXECUTOR = ThreadPoolExecutor(NUM_THREADS)
OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))

# this is your long running processing function
# takes in your arguments from the /queue-task endpoint
def a_long_running_task(*args):
    time_to_wait, output_file_name = int(args[0][0]), args[0][1]
    output_string = 'sleeping for {0} seconds. File: {1}'.format(time_to_wait, output_file_name)
    print(output_string)
    time.sleep(time_to_wait)
    filename = os.path.join(OUTPUT_DIR, output_file_name)
    # here we are writing to a gzipped file to save space and decrease size of file to be sent on network
    with gzip.open(filename, 'wb') as f:
        f.write(output_string)
    print('finished writing {0} after {1} seconds'.format(output_file_name, time_to_wait))

# This is a route that starts the task and then gives them the file name for reference
@app.route('/queue-task/<wait>')
def queue_task(wait):
    output_file_name = str(uuid4()) + '.csv'
    EXECUTOR.submit(a_long_running_task, [wait, output_file_name])
    return jsonify({'filename': output_file_name})

# this takes the file name and returns if exists, otherwise notifies it is not yet done
@app.route('/getfile/<name>')
def get_output_file(name):
    file_name = os.path.join(OUTPUT_DIR, name)
    if not os.path.isfile(file_name):
        return jsonify({"message": "still processing"})
    # read without gzip.open to keep it compressed
    with open(file_name, 'rb') as f:
        resp = Response(f.read())
    # set headers to tell encoding and to send as an attachment
    resp.headers["Content-Encoding"] = 'gzip'
    resp.headers["Content-Disposition"] = "attachment; filename={0}".format(name)
    resp.headers["Content-type"] = "text/csv"
    return resp


if __name__ == '__main__':
    app.run()
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!