Delete an uploaded file after downloading it from Flask
There are several ways to do this.
send_file
and then immediately delete (Linux only)
Flask has an after_this_request
decorator which could work for this use case:
@app.route('/files/<filename>/download')
def download_file(filename):
file_path = derive_filepath_from_filename(filename)
file_handle = open(file_path, 'r')
@after_this_request
def remove_file(response):
try:
os.remove(file_path)
file_handle.close()
except Exception as error:
app.logger.error("Error removing or closing downloaded file handle", error)
return response
return send_file(file_handle)
The issue is that this will only work on Linux (which lets the file be read even after deletion if there is still an open file pointer to it). It also won't always work (I've heard reports that sometimes send_file
won't wind up making the kernel call before the file is already unlinked by Flask). It doesn't tie up the Python process to send the file though.
Stream file, then delete
Ideally though you'd have the file cleaned up after you know the OS has streamed it to the client. You can do this by streaming the file back through Python by creating a generator that streams the file and then closes it, like is suggested in this answer:
def download_file(filename):
file_path = derive_filepath_from_filename(filename)
file_handle = open(file_path, 'r')
# This *replaces* the `remove_file` + @after_this_request code above
def stream_and_remove_file():
yield from file_handle
file_handle.close()
os.remove(file_path)
return current_app.response_class(
stream_and_remove_file(),
headers={'Content-Disposition': 'attachment', 'filename': filename}
)
This approach is nice because it is cross-platform. It isn't a silver bullet however, because it ties up the Python web process until the entire file has been streamed to the client.
Clean up on a timer
Run another process on a timer (using cron
, perhaps) or use an in-process scheduler like APScheduler and clean up files that have been on-disk in the temporary location beyond your timeout (e. g. half an hour, one week, thirty days, after they've been marked "downloaded" in RDMBS)
This is the most robust way, but requires additional complexity (cron, in-process scheduler, work queue, etc.)
Remove file after Flask serves it
after_request
runs after the view returns but before the response is sent. Sending a file may use a streaming response; if you delete it before it's read fully you can run into errors.
This is mostly an issue on Windows, other platforms can mark a file deleted and keep it around until it not being accessed. However, it may still be useful to only delete the file once you're sure it's been sent, regardless of platform.
Read the file into memory and serve it, so that's it's not being read when you delete it later. In case the file is too big to read into memory, use a generator to serve it then delete it.
@app.route('/download_and_remove/<filename>')
def download_and_remove(filename):
path = os.path.join(current_app.instance_path, filename)
def generate():
with open(path) as f:
yield from f
os.remove(path)
r = current_app.response_class(generate(), mimetype='text/csv')
r.headers.set('Content-Disposition', 'attachment', filename='data.csv')
return r
Flask: delete file from server after send_file() is completed
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
Flask unable to delete file after send_file()
The issue here is that @after_this_request
causes delete_file()
to be passed in the "proposed" response object produced by test_func()
. @after_this_request
gives delete_file()
the opportunity to modify the response before it is sent.
So, your os.remove('test.txt')
is actually being called before send_file()
has completed.
You can see this in the help for @after_this_request
. It says "The function is passed the response object and has to return the same or a new one." So you can see from that the code will be run before the response is actually returned.
For a solution, see this question.
Flask - delete file after download
Resolved with this code.
@app.route('/path/<name>')
def download(name):
file_path ="/path/"+name
file_handle = open(file_path, 'r')
@after_this_request
def remove_file(response):
os.remove("/path/"+name)
return response
return send_file(file_handle)
Send a file to the user, then delete file from server
Since send_file
already returns the response from the endpoint, it is no longer possible to execute code afterwards.
However, it is possible to write the file to a stream before the file is deleted and then to send the stream in response.
from flask import send_file
import io, os, shutil
@app.route('/download/<path:filename>')
def download(filename):
path = os.path.join(
app.static_folder,
filename
)
cache = io.BytesIO()
with open(path, 'rb') as fp:
shutil.copyfileobj(fp, cache)
cache.flush()
cache.seek(0)
os.remove(path)
return send_file(cache, as_attachment=True, attachment_filename=filename)
In order to achieve better use of the memory for larger files, I think a temporary file is more suitable as a buffer.
from flask import send_file
import os, shutil, tempfile
@app.route('/download/<path:filename>')
def download(filename):
path = os.path.join(
app.static_folder,
filename
)
cache = tempfile.NamedTemporaryFile()
with open(path, 'rb') as fp:
shutil.copyfileobj(fp, cache)
cache.flush()
cache.seek(0)
os.remove(path)
return send_file(cache, as_attachment=True, attachment_filename=filename)
I hope your conditions are met.
Have fun implementing your project.
Remove file after Flask serves it
after_request
runs after the view returns but before the response is sent. Sending a file may use a streaming response; if you delete it before it's read fully you can run into errors.
This is mostly an issue on Windows, other platforms can mark a file deleted and keep it around until it not being accessed. However, it may still be useful to only delete the file once you're sure it's been sent, regardless of platform.
Read the file into memory and serve it, so that's it's not being read when you delete it later. In case the file is too big to read into memory, use a generator to serve it then delete it.
@app.route('/download_and_remove/<filename>')
def download_and_remove(filename):
path = os.path.join(current_app.instance_path, filename)
def generate():
with open(path) as f:
yield from f
os.remove(path)
r = current_app.response_class(generate(), mimetype='text/csv')
r.headers.set('Content-Disposition', 'attachment', filename='data.csv')
return r
Flask: How to delete files from the server
I solved the issue with the following directory structure:
.
├── app.py
├── templates
│ └── delete_files.html
├── wellness.csv
└── wellness.jsonl
As you can see I have two files called wellness.csv
and wellness.jsonl
in the directory where I have placed my app.py
file. The name wellness
will be passed from the template and these two files will be deleted from the directory.
app.py
:
import os
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def search():
return render_template('delete_files.html')
@app.route('/remove/<file_id>')
def remove(file_id):
filename_jsonl = f"{file_id}.jsonl"
filename_csv = f"{file_id}.csv"
try:
os.remove(filename_jsonl)
os.remove(filename_csv)
return "Files are deleted successfully"
except Exception as e:
return f"Error in deleting files: {e}"
delete_files.html
:
<html>
<head>
<title>Delete files using button click in Flask</title>
</head>
<body>
<a href="/remove/wellness" id="remove" class="btn btn-success mr-2">Remove</a>
</body>
</html>
Output:
After clicking the delete button I see the message Files are deleted successfully
.
The folder structure after deletion of the files:
.
├── app.py
└── templates
└── delete_files.html
Update
If you want to redirect to root url after successful deletion you can use redirect
method like below:
import os
from flask import Flask, render_template, redirect, url_for
app = Flask(__name__)
@app.route('/')
def search():
return render_template('delete_files.html')
@app.route('/remove/<file_id>')
def remove(file_id):
filename_jsonl = f"{file_id}.jsonl"
filename_csv = f"{file_id}.csv"
try:
os.remove(filename_jsonl)
os.remove(filename_csv)
return redirect(url_for('search'))
except Exception as e:
return f"Error in deleting files: {e}"
Related Topics
Python Multiprocessing on Windows, If _Name_ == "_Main_"
How to Use Qscrollarea to Make Scrollbars Appear
How to Implement Server Push in Flask Framework
Custom Filter in Django Admin on Django 1.3 or Below
Jupyter Notebook with Python 3.8 - Notimplementederror
Python & MySQL: Unicode and Encoding
Why Does Str(Float) Return More Digits in Python 3 Than Python 2
List Directory Tree Structure in Python
Databaseerror: Current Transaction Is Aborted, Commands Ignored Until End of Transaction Block
How to Retrieve Inserted Id After Inserting Row in SQLite Using Python
How to Stop Flask Application Without Using Ctrl-C
How to Split Strings into Text and Number
Matching Nested Structures with Regular Expressions in Python
Decorating Class Methods - How to Pass the Instance to the Decorator