从 Flask 下载后删除上传的文件

新手上路,请多包涵

我目前正在开发一个小型 Web 界面,它允许不同的用户上传文件、转换他们上传的文件以及下载转换后的文件。转换的细节对我的问题并不重要。

我目前使用flask-uploads来管理上传的文件,并将它们存储在文件系统中。一旦用户上传并转换文件,就会有各种漂亮的按钮来删除文件,这样上传文件夹就不会填满。

我不认为这是理想的。我真正想要的是在下载文件后立即将其删除。我会满足于会话结束时删除的文件。

我花了一些时间试图弄清楚如何做到这一点,但我还没有成功。这似乎不是一个不常见的问题,所以我认为一定有一些我遗漏的解决方案。有没有人有办法解决吗?

原文由 seaturtlecook 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

有几种方法可以做到这一点。

send_file 然后立即删除(仅限Linux)

Flask 有一个 after_this_request 装饰器,可以用于这个用例:

 @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)

问题是 这只适用于 Linux (如果仍然有一个打开的文件指针指向它,即使在删除后也可以读取文件)。它也不会 总是 有效(我听说有时 send_file 在文件已经被 Flask 取消链接之前不会结束内核调用)。不过,它不会占用 Python 进程来发送文件。

流文件,然后删除

理想情况下,您会在 知道 操作系统已将文件流式传输到客户端后清理文件。您可以通过创建一个流式传输文件然后关闭它的生成器,通过 Python 流式传输文件来完成此操作,就像 此答案中所建议的那样

 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}
    )

这种方法很好,因为它是跨平台的。然而,它不是灵丹妙药,因为它会占用 Python Web 进程,直到整个文件都流式传输到客户端。

定时清理

在计时器上运行另一个进程(可能使用 cron )或使用进程内调度程序,如 APScheduler 并清理超时(例如半小时,在 RDMBS 中标记为“已下载”后一周,三十天)

这是最稳健的方式,但需要额外的复杂性(cron、进程内调度程序、工作队列等)

原文由 Sean Vieira 发布,翻译遵循 CC BY-SA 4.0 许可协议

您还可以将文件的数据存储在内存中,将其删除,然后提供内存中的数据。

例如,如果您提供 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')

(以上我只是假设它是 PDF,但如果需要,您可以通过编程方式 获取 mimetype

原文由 Garrett 发布,翻译遵循 CC BY-SA 4.0 许可协议

推荐问题