44

I'm using python 3.5 and flask 0.10.1 and liking it, but having a bit of trouble with send_file. I ultimately want to process a pandas dataframe (from Form data, which is unused in this example but necessary in the future) and send it to download as a csv (without a temp file). The best way to accomplish this I've seen is to us StringIO.

Here is the code I'm attempting to use:

@app.route('/test_download', methods = ['POST'])
def test_download():
    buffer = StringIO()
    buffer.write('Just some letters.')
    buffer.seek(0)
    return send_file(buffer, as_attachment = True,\
    attachment_filename = 'a_file.txt', mimetype = 'text/csv')

A file downloads with the proper name, however the file is completely blank.

Any ideas? Issues with encoding? Has this been answered elsewhere? Thanks!

Daniel Hitchcock
  • 633
  • 1
  • 6
  • 11

4 Answers4

59

The issue here is that in Python 3 you need to use StringIO with csv.write and send_file requires BytesIO, so you have to do both.

@app.route('/test_download')
def test_download():
    row = ['hello', 'world']
    proxy = io.StringIO()
    
    writer = csv.writer(proxy)
    writer.writerow(row)
    
    # Creating the byteIO object from the StringIO Object
    mem = io.BytesIO()
    mem.write(proxy.getvalue().encode())
    # seeking was necessary. Python 3.5.2, Flask 0.12.2
    mem.seek(0)
    proxy.close()

    return send_file(
        mem,
        as_attachment=True,
        attachment_filename='test.csv',
        mimetype='text/csv'
    )
Smart Manoj
  • 4,375
  • 4
  • 27
  • 51
Radu
  • 8,304
  • 8
  • 51
  • 89
  • 1
    This should be the accepted answer since it is more generic in sense of exporting/download CSV file by using send_file() method. – Sanchit Apr 09 '18 at 10:06
  • In the encode I added `"utf-8-sig"`, or you might want `"utf-8"`. Also see https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom – Charles L. Apr 07 '21 at 22:22
23

I guess you should write bytes.

from io import BytesIO    

from flask import Flask, send_file


app = Flask(__name__)


@app.route('/test_download', methods=['POST'])
def test_download():
    # Use BytesIO instead of StringIO here.
    buffer = BytesIO()
    buffer.write(b'jJust some letters.')
    # Or you can encode it to bytes.
    # buffer.write('Just some letters.'.encode('utf-8'))
    buffer.seek(0)
    return send_file(buffer, as_attachment=True,
                     attachment_filename='a_file.txt',
                     mimetype='text/csv')


if __name__ == '__main__':
    app.run(debug=True)
lord63. j
  • 4,220
  • 2
  • 19
  • 29
  • Yes that works-- I just learned that flask on python 3 won't work with StringIO. Follow up question--do you know a way to convert a pandas dataframe into into a bytes csv for download? – Daniel Hitchcock Mar 01 '16 at 14:47
  • 1
    @DanielHitchcock Hi, you should provide a Minimal, Complete, and Verifiable example so I can reproduce this problem(like this question, but with the pandas dataframe example), I don't quiet familiar with pandas so my current answer is no. You may ask another question if necessary. – lord63. j Mar 02 '16 at 13:16
  • The crucial part is "buffer.seek(0)" - I had a problem with an excel-file from pandas and it was solved with this line – Fips Jul 14 '21 at 09:50
1

make_response

  • To get Flask to download a csv file to the user, we pass a csv string to the make_response function, which returns a Response object.
  • Then we add a Header which tells the browser to accept the file as a download.
  • The Mimetype also must be set to text/csv in order to get the web browser to save it in something other than an html document.
from flask import Flask, make_response  
app = Flask(__name__)

@app.route('/test_download', methods=['POST'])
def test_download():
    with StringIO() as buffer:
        # forming a StringIO object  
        buffer = StringIO()
        buffer.write('Just some letters.')
        # forming a Response object with Headers to return from flask 
        response = make_response(buffer.getvalue())
        response.headers['Content-Disposition'] = 'attachment; filename=namaste.csv'
        response.mimetype = 'text/csv'
        # return the Response object
        return response

P.S. It is preferred to use python's built-in csv library to deal with csv files

References

Namaste

Deepam Gupta
  • 1,714
  • 1
  • 18
  • 29
0

if someone use python 2.7 with Flask and got the error about the module StringIO by importing it. This post can help you to solve your problem.

If you are importing String IO module, you can just change the import syntax by using this : from io import StringIO instead from StringIO import StringIO.

You can Also use from io import BytesIO if you are using image or some others ressource.

Thank you

Lamine BA
  • 99
  • 1
  • 8