Solución:
Hay varias formas de hacer esto.
send_file
y luego eliminar inmediatamente (solo Linux)
El matraz tiene un after_this_request
decorador que podría funcionar para este caso de uso:
@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)
El problema es que esto solo funcionará en Linux (lo que permite que el archivo se lea incluso después de la eliminación si todavía hay un puntero de archivo abierto). Tampoco lo hará siempre trabajo (he escuchado informes de que a veces send_file
no terminará haciendo la llamada del kernel antes de que Flask ya haya desvinculado el archivo). Sin embargo, no obstaculiza el proceso de Python para enviar el archivo.
Transmitir archivo, luego eliminar
Idealmente, aunque tendrías que limpiar el archivo después de ti saber el sistema operativo lo ha transmitido al cliente. Puede hacer esto transmitiendo el archivo a través de Python creando un generador que transmita el archivo y luego lo cierre, como se sugiere en esta respuesta:
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}
)
Este enfoque es bueno porque es multiplataforma. Sin embargo, no es una solución milagrosa, porque bloquea el proceso web de Python hasta que todo el archivo se ha transmitido al cliente.
Limpiar con temporizador
Ejecute otro proceso en un temporizador (usando cron
, tal vez) o use un programador en proceso como APScheduler y limpie los archivos que han estado en el disco en la ubicación temporal más allá de su tiempo de espera (por ejemplo, media hora, una semana, treinta días, después de que se hayan marcado como “descargados” en RDMBS)
Esta es la forma más sólida, pero requiere una complejidad adicional (cron, programador en proceso, cola de trabajo, etc.)
También puede almacenar el archivo en la memoria, eliminarlo y luego servir lo que tiene en la memoria.
Por ejemplo, si estuviera sirviendo un PDF:
import io
import os
@app.route('/download')
def download_file():
file_path = get_path_to_your_file()
return_data = io.BytesIO()
with open(file_path, 'rb') as fo:
return_data.write(fo.read())
# (after writing, cursor will be at last byte, so move it to start)
return_data.seek(0)
os.remove(file_path)
return send_file(return_data, mimetype="application/pdf",
attachment_filename="download_filename.pdf")
(arriba, solo asumo que es PDF, pero puede obtener el tipo MIME mediante programación si lo necesita)