20

我目前正在开发一个服务器端 json 接口,其中有几个临时文件在请求期间进行操作。

我当前在请求结束时清理这些文件的解决方案如下所示:

@app.route("/method",methods=['POST'])
def api_entry():
    with ObjectThatCreatesTemporaryFiles() as object:
        object.createTemporaryFiles()
        return "blabalbal"

在这种情况下,清理在 object.__exit__() 中进行

但是在某些情况下,我需要向客户端返回一个临时文件,在这种情况下,代码如下所示:

@app.route("/method",methods=['POST'])
def api_entry():
    with ObjectThatCreatesTemporaryFiles() as object:
        object.createTemporaryFiles()
        return send_file(object.somePath)

这目前不起作用,因为当我进行清理时,烧瓶正在读取文件并将其发送给客户端。¨ 我该如何解决这个问题?

编辑:我忘了提到这些文件位于临时目录中。

4

5 回答 5

14

我使用的方法是在响应完成后使用弱引用删除文件。

import shutil
import tempfile
import weakref

class FileRemover(object):
    def __init__(self):
        self.weak_references = dict()  # weak_ref -> filepath to remove

    def cleanup_once_done(self, response, filepath):
        wr = weakref.ref(response, self._do_cleanup)
        self.weak_references[wr] = filepath

    def _do_cleanup(self, wr):
        filepath = self.weak_references[wr]
        print('Deleting %s' % filepath)
        shutil.rmtree(filepath, ignore_errors=True)

file_remover = FileRemover()

在烧瓶电话中,我有:

@app.route('/method')
def get_some_data_as_a_file():
    tempdir = tempfile.mkdtemp()
    filepath = make_the_data(dir_to_put_file_in=tempdir)
    resp = send_file(filepath)
    file_remover.cleanup_once_done(resp, tempdir)
    return resp

这是相当普遍的,并且作为一种方法可以在我使用的三个不同的 python web 框架中工作。

于 2015-08-21T03:44:18.297 回答
11

如果您使用的是 Flask 0.9 或更高版本,您可以使用after_this_request装饰器:

@app.route("/method",methods=['POST'])
def api_entry():
    tempcreator = ObjectThatCreatesTemporaryFiles():
    tempcreator.createTemporaryFiles()

    @after_this_request
    def cleanup(response):
        tempcreator.__exit__()
        return response

    return send_file(tempcreator.somePath)

编辑

由于这不起作用,您可以尝试cStringIO改用(这假设您的文件足够小以适合内存):

@app.route("/method", methods=["POST"])
def api_entry():
    file_data = dataObject.createFileData()
    # Simplest `createFileData` method:  
    # return cStringIO.StringIO("some\ndata")
    return send_file(file_data,
                        as_attachment=True,
                        mimetype="text/plain",
                        attachment_filename="somefile.txt")

或者,您可以像现在一样创建临时文件,但依赖于您的应用程序来删除它们。相反,设置一个 cron 作业(或计划任务,如果您在 Windows 上运行)每隔一小时左右运行一次,并删除半小时前创建的临时目录中的文件。

于 2012-11-12T14:12:09.213 回答
3

有点晚了,但这是我使用madjar 的建议所做的(以防其他人遇到此问题)。这是我使用的一个小辅助函数(它以 PyExcelerate Workbook 对象作为参数),您可以根据自己的情况进行调整。只需更改创建/构建 tempfile.TemporaryFile 的方式即可!在 Windows 8.1 和 Ubuntu 12.04 上测试。

def xlsx_to_response(wb, filename):
    f = tempfile.TemporaryFile()
    wb._save(f)
    f.seek(0)
    response = send_file(f, as_attachment=True, attachment_filename=filename,
                         add_etags=False)

    f.seek(0, os.SEEK_END)
    size = f.tell()
    f.seek(0)
    response.headers.extend({
        'Content-Length': size,
        'Cache-Control': 'no-cache'
    })
    return response
于 2014-04-23T04:07:30.870 回答
3

我有两个解决方案。


第一个解决方案是在__exit__方法中删除文件,但不关闭它。这样,文件对象仍然可以访问,您可以将其传递给send_file.

这仅在您不使用时才有效X-Sendfile,因为它使用文件名。


第二种解决方案是依赖垃圾收集器。您可以传递给send_file将在删除时清理文件的文件对象(__del__方法)。这样,只有在从 python 中删除文件对象时才会删除文件。TemporaryFile如果你还没有,你可以使用它。

于 2012-11-12T14:15:10.953 回答
0

Windows / Linux / Mac 兼容解决方案

我尝试过weakrefs、flask 内置装饰器,但没有任何效果。

唯一适用于每个系统的想法是使用 io.BytesIO 在内存中创建一个临时文件

import os
import io
import tempfile
from multiprocessing import Process

import flask


def background_job(callback):
    task = Process(target=callback())
    task.start()

def send_temp_file(file_path: str, temp_dir: tempfile.TemporaryDirectory, remove_dir_after_send=True):
    with open(file_path, "rb") as f:
        content = io.BytesIO(f.read())
    response = flask.send_file(content,
                               as_attachment=True,
                               attachment_filename=os.path.split(file_path)[0])
    if remove_dir_after_send:
        background_job(temp_dir.cleanup)
    return response



app = flask.Flask(__name__)


@app.route("/serve_file/", methods=["GET"])
def serve_file():
    temp_dir = tempfile.TemporaryDirectory()
    file_path = os.path.join(temp_dir.name, "test.txt")
    with open(file_path, "w") as f:
        f.write("Hello World!")

    return send_temp_file(file_path, temp_dir)


if __name__ == "__main__":
    app.run(port=1337)
于 2021-07-01T09:18:36.787 回答