Forums

Flask, send_file, BytesIO & uWSGI

I am using Flask's send_file method to allow a user to download a dynamically generated Excel file. It is working fine in my local development environment. When I try to run on PythonAnywhere, I get the below error:

SystemError: <built-in function uwsgi_sendfile> returned a result with an error set

It is the same as is discussed here, in length.

The same issue is discussed here, where your team indicate that send_file is not supported.

However, in this thread, your team state that send_file is supported.

Here, it is stated that send_file requires a file pointer rather than a BytesIO object. A BytesIO object is actually supported by Flask's send_file method (plenty of examples & documentation online, and its working for me locally), but perhaps the poster meant that it is not supported by PythonAnywhere.

Can I check what the true status for this is? I guess I can't do the recommended fix of setting --wsgi-disable-file-wrapper as I can't modify the uWSGI config. Is there another way?

Thank you!

You're confusing the uwsgi send_file function (your first and second links) with the flask send_file function.

The flask send_file function sometimes tries to delegate to the wsgi file wrapper that is built in to the wsgi server. When it does that with a BytesIO object, it does not work because the uWSGI file wrapper does not support file-like objects, only real files. Here is a simple example Flask app that only serves a BytesIO object by bypassing the standard Flask delegation to the uWSGI file wrapper:

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)

Fantastic, thank you for clearing that up for me and for the quick response! I'll try it out as soon as I can.

I tried this and it worked. Thank you!

I had to tweak the FileWrapper import to:

from werkzeug.wsgi import FileWrapper

is there a way to set a filename and extension? This is great as I was banging my head against the wall wondering why i couldnt use send_file, this is ok but I really need a filename...

It looks like this StackOverflow question covers the headers you need to set to do that.

That works great, thanks!

Thanks, worked great! It is a nasty error though, took me hours to find this post which finally solved. It would be nice if the error log said something clarifying like 'Flask send_file() with file-like objects not supported in python-anywhere'

Agreed, that would certainly make life easier! Unfortunately none of our code gets executed as part of the code path when the problem happens, though, so we can't introduce a custom error. We're hoping to be able to fix the underlying problem soon, though.

We have written a help page to help with this in future http://help.pythonanywhere.com/pages/FlaskSendFileBytesIO/

Hello Team,

I went through this thread and the stackoverflow link and yet my error is not solved. I want to provide downloadable excel file and I get Could you please provide the workaround to provide downloadable excel file as Response?

What code are you using?

.

    output = BytesIO()

    writer = pd.ExcelWriter(output, engine='xlsxwriter')
    for i, A in enumerate(trade_list):
        A.to_excel(writer,sheet_name="{0}".format(cnames[i]))


    writer.close()


    output.seek(0)
    w = FileWrapper(output)

    return Response(w, mimetype="testing.xlsx", direct_passthrough=True)

[edit by admin: formatting]

trade_list is a list of dataframes

I'm not sure that you should have that writer.close() there; does removing it (or perhaps replacing it with writer.flush() fix the problem? If not, what's the full error message that you're getting?

I made the change as you said and get the following error:

AttributeError: '_XlsxWriter' object has no attribute 'flush'

I made the following change to my original code and it works

    return Response(w, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", direct_passthrough=True)

However please let me know how can I give name to the excel file. Currently it's using the url name, meaning the output is available at www.quantpanda/BacktestAnalysis2 so the filename is BacktestAnalysis2.xlsx everytime I download it.

Never mind. The following post helped. Glenn is omniscient.

https://stackoverflow.com/questions/50087728/alternative-of-send-file-in-flask-on-pythonanywhere