Python/Flask - ValueError: I/O operation on closed file Python/Flask - ValueError: I/O operation on closed file flask flask

Python/Flask - ValueError: I/O operation on closed file


Use send_file with the filename, it'll open, serve and close it the way you expect.

@app.route('/pdf')def pdfStuff():    return send_file('pdffile.pdf')


Despite @iurisilvio's answer solves this specific problem, is not a useful answer in any other case. I was struggling with this myself.

All the following examples are throwing ValueError: I/O operation on closed file. but why?

@app.route('/pdf')def pdfStuff():    with open('pdffile.pdf', 'rb') as static_file:        return send_file(static_file, attachment_filename='pdffile.pdf')@app.route('/pdf')def pdfStuff():    static_file = open('pdffile.pdf','rb')    try:        return send_file(static_file, attachment_filename='pdffile.pdf')    finally:        static_file.close()

I am doing something slightly different. Like this:

@page.route('/file', methods=['GET'])def build_csv():    # ... some query ...    ENCODING = 'utf-8'    bi = io.BytesIO()    tw = io.TextIOWrapper(bi, encoding=ENCODING)    c = csv.writer(tw)    c.writerow(['col_1', 'col_2'])    c.writerow(['1', '2'])    bi.seek(0)    return send_file(bi,                     as_attachment=True,                     attachment_filename='file.csv',                     mimetype="Content-Type: text/html; charset={0}".format(ENCODING)                     )

In the first two cases, the answer is simple:

You give a stream to send_file, this function will not immediatelly transmit the file, but rather wrap the stream and return it to Flask for future handling. Your pdfStuff function will allready return before Flask will start handling your stream, and in both cases (with and finally) the stream will be closed before your function returns.

The third case is more tricky (but this answer pointed me in the right direction: Why is TextIOWrapper closing the given BytesIO stream?). In the same fashion as explained above, bi is handled only after build_csv returns. Hence tw has allready been abandoned to the garbage collector. When the collector will destroy it, tw will implicitly close bi. The solution to this one is tw.detach() before returning (this will stop TextIOWrapper from affecting the stream).

Side note (please correct me if I'm wrong):This behaviour is limiting, unless when send_file is provided with a file-like object it will handle the closing on its own. It is not clear from the documentation (https://flask.palletsprojects.com/en/0.12.x/api/#flask.send_file) if closing is handled. I would assume so (there are some .close() present in the source code + send_file uses werkzeug.wsgi.FileWrapper which has .close() implemented too), in which case your approach can be corrected to:

@app.route('/pdf')def pdfStuff():    return send_file(open('pdffile.pdf','rb'), attachment_filename='pdffile.pdf')

Ofcourse in this case, would be stright forward to provide the file name. But in other cases, may be needed to wrap the file stream in some manipulation pipeline (decode / zip)