如何清理send_file使用的临时文件?

时间:2012-11-12 13:25:54

标签: python flask web-frameworks

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

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

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

在这种情况下,清理会在对象.__ exit __()

中使用花边

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

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

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

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

5 个答案:

答案 0 :(得分:8)

如果您使用的是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上运行一个计划任务)每小时左右运行一次,并删除临时目录中超过半小时创建的文件。

答案 1 :(得分:7)

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

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框架。

答案 2 :(得分:3)

我有两个解决方案。


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

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


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

答案 3 :(得分:2)

这有点晚了,但这是我使用madjar's建议所做的事情(如果有其他人遇到此事)。这是我使用的一个小辅助函数(它需要一个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

答案 4 :(得分:0)

Windows / Linux / Mac 兼容解决方案

我尝试过弱引用、烧瓶内置装饰器,但没有任何效果。

在每个系统中都有效的唯一想法是使用 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)