Alternative of send_file() in flask on Pythonanywhere?
Asked Answered
G

2

6

I am new to python and still learning. I created a small python 3.6 Flask webapp on pythonanwhere and found out that send_file() is not working on pythonanywhere servers. I am actively looking for an alternative to download an excel file directly on the user machine. I also tried Response but it is not giving desired output. I read alot about it online and found that the send_file works fine if we set below

wsgi-disable-file-wrapper = True

However, i don't know where to set this as i couldn't find uWsgi.ini file where i could update this line.

Below are the methods which i tried but they failed, please help

SEND_FILE() configuration: ->>> Not running..

    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    workbook = writer.book
    output.seek(0)
    return send_file(output,attachment_filename="testing.xlsx",as_attachment=True)

Output Error:

return environ.get('wsgi.file_wrapper', FileWrapper)(file, buffer_size)
SystemError: <built-in function uwsgi_sendfile> returned a result with an error set

With Response configuration:

writer = pd.ExcelWriter("abc.xlsx", engine='xlsxwriter')

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

Output error:

Error running WSGI application
TypeError: '_XlsxWriter' object is not iterable
File "/home/hridesh1987/.virtualenvs/myproject/lib/python3.6/site-packages/werkzeug/wsgi.py", line 870, in __next__return self._next()
File "/home/hridesh1987/.virtualenvs/myproject/lib/python3.6/site-packages/werkzeug/wrappers.py", line 83, in _iter_encoded
for item in iterable:
Great answered 29/4, 2018 at 14:36 Comment(2)
how did you find out that send_file doesn't work on PythonAnywhere? to my knowledge it should work?Salts
@Salts see pythonanywhere.com/forums/topic/8612Putumayo
R
11

I raised the same issue on the PythonAnywhere forums and they gave me this response. Kudos to 'glenn' of the PythonAnywhere staff.

Copy-pasted:

from io import BytesIO
from flask import Flask, Response
from werkzeug import FileWrapper

app = Flask(__name__)

@app.route('/')
def hello_world():
    b = BytesIO(b"blah blah blah")
    w = FileWrapper(b)
    return Response(w, mimetype="text/plain", direct_passthrough=True)

I adapted it slightly for my usage. I set the filename via the Content-Disposition header. I also had to tweak the FileWrapper import, and data is already a BytesIO object in my code:

from flask import Response
from werkzeug.wsgi import FileWrapper

def send_excel_file(data, filename):
    # See: https://www.pythonanywhere.com/forums/topic/13570/
    file_wrapper = FileWrapper(data)
    headers = {
        'Content-Disposition': 'attachment; filename="{}"'.format(filename)
    }
    response = Response(file_wrapper,
                        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                        direct_passthrough=True,
                        headers=headers)
    return response
Rident answered 29/11, 2018 at 9:46 Comment(0)
M
3

From the mentioned forum in Mat's answer I verified that:

[send_file()] does not work because the uWSGI file wrapper does not support file-like objects, only real files

...but applying Mat's solution still throws me a ValueError: I/O operation on closed file. Even with FileWrapper class.

This way is even simpler: If you use a file_pointer based on io eg. io.StringIO() you must use Response() instead. Not with a fp but sending the content directly. Based on your code:

with BytesIO() as output:
    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    output.seek(0)
    headers = {"Content-disposition": "attachment; filename=testing.xlsx"}
    mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    return Response(output.read(), mimetype=mimetype, headers=headers)
Moffat answered 13/10, 2020 at 5:29 Comment(2)
what does ` workbook = writer.book` do, looks like its not being usedLa
You are correct, but that part was in his code. I tried not to change his example, but now it's fixed. The important part is the Response() change, reading io.BytesIO() object, so I didn't pass a pointer but bytes() instead.Moffat

© 2022 - 2024 — McMap. All rights reserved.