RishiC
RishiC

Reputation: 826

Flask: delete file from server after send_file() is completed

I have a Flask backend which generates an image based on some user input, and sends this image to the client side using the send_file() function of Flask.

This is the Python server code:

@app.route('/image',methods=['POST'])
def generate_image():
    cont = request.get_json()
    t=cont['text']
    print(cont['text'])
    name = pic.create_image(t) //A different function which generates the image
    time.sleep(0.5)
    return send_file(f"{name}.png",as_attachment=True,mimetype="image/png")

I want to delete this image from the server after it has been sent to the client.

How do I achieve it?

Upvotes: 2

Views: 3269

Answers (5)

AdityaDN
AdityaDN

Reputation: 385

After trying all the methods:

  1. @app.after_request is run after every request used and hence not only do we have to narrow it down but also prevent any conflicts
  2. @after_this_request explictly staes

This is useful to modify response objects. The function is passed the response object and has to return the same or a new one.

ie it is run before the request is returned to modify the response, hence it is no different from adding that code at the end of the function instead of return. This only works in Linux because file pointers can exist even after file deletion there.

  1. tempfiles are usually created in system directories and hence only allow deletion of common file extensions any other extensions are denied permission to delete.

So what I found best is to open the image as a file object to read the contents,delete the file and sent this contents over

with open(f"{name}.png", 'rb') as f:
        contents = f.read()
os.remove(f"{name}.png")

return send_file(
        io.BytesIO(contents),
        as_attachment=True,
        attachment_filename=f"{name}.png",
        mimetype="image/png"
    )

Upvotes: 2

svenmk
svenmk

Reputation: 90

I had the same issues as some other commenters here:

  1. The @after_this_request solution doesn't help, the file cannot be accessed.
  2. The @app.after_request solution appeared to be too complicated and too obscure in my opinion.

Finally I came up with a solution (sorry the example is from my code, so a little bit different from the original question):

@bp.route('/download/<int:id>')
def download(id: int):
    with db.session() as ses:
        data = ses.scalars( ... fetch data from db by id...).one_or_none()
        if session_data is None:
            abort(404, f"Data with id {id} unknown.")
        try:
            with tempfile.NamedTemporaryFile(delete=False, suffix=".tmp") as fid:
                tmp_file = Path(fid.name)
                write_data_to_file(tmp_file, data)
                fid.seek(0)
                file_content = io.BytesIO(fid.read())
        finally:
            tmp_file.unlink()

        return send_file(
            file_content,
            "application/octet-stream",
            as_attachment=True,
            download_name=f"Data_{id}.tmp")

This will write the data fetched from db to a file (unfortunatey the writer function in my case only accepts file names...). Since my code must also run on Windows, I need to do the delete=False ... .unlink() trick. If this weren't the case, one could also just let the context manager do the job.

The actual solution is in reading the file content into an io.BytesIO buffer and then closing and deleting the file before returning.

Upvotes: 0

rockstaedt
rockstaedt

Reputation: 61

Another way would be to include the decorator in the route. Thus, you do not need to check for the endpoint. Just import after_this_request from the flask lib.

from flask import after_this_request


@app.route('/image',methods=['POST'])
def generate_image():
    @after_this_request
    def delete_image(response):
        try:
            os.remove(image_name)
        except Exception as ex:
            print(ex)
        return response

    cont = request.get_json()
    t=cont['text']
    print(cont['text'])
    name = pic.create_image(t) //A different function which generates the image
    time.sleep(0.5)
    return send_file(f"{name}.png",as_attachment=True,mimetype="image/png")

Upvotes: 2

RishiC
RishiC

Reputation: 826

Ok I solved it. I used the @app.after_request and used an if condition to check the endpoint,and then deleted the image

@app.after_request
def delete_image(response):
    global image_name
    if request.endpoint=="generate_image": //this is the endpoint at which the image gets generated
        os.remove(image_name)
    return response

Upvotes: 5

whichperson
whichperson

Reputation: 42

You could have another function delete_image() and call it at the bottom of the generate_image() function

Upvotes: -2

Related Questions