Before anyone says that this is a duplicate, I do not think it is because I have looked at the similar questions and they have not helped me!
I am creating a Flask server in python, and I need to be able to have a url that shows a pdf.
I tried to use the following code:
@app.route('/pdf')
def pdfStuff():
with open('pdffile.pdf', 'rb') as static_file:
return send_file(static_file, attachment_filename='pdffile.pdf')
This is supposed to make it so when I go to /pdf
it will show the pdf file pdffile.pdf
.
However, this does not work because when I run the code I get this error:
ValueError: I/O operation on closed file
How is this the case? My return statement is inside the with statement, therefore shouldn't the file be open?
I tried to use a normal static_file = open(...)
and used try
and finally
statements, like this:
static_file = open('pdffile.pdf','rb')
try:
return send_file(static_file, attachment_filename='pdffile.pdf')
finally:
static_file.close()
The same error happens with the above code, and I have no idea why. Does anyone know what I could be doing wrong?
Sorry if I am being stupid and there is something simple that I made a mistake with!
Thank you very much in advance !!
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)
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With