Как очистить временный файл, используемый с send_file?

В настоящее время я разрабатываю интерфейс 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)

В настоящее время это не работает, потому что когда я делаю очистку, колба находится в процессе чтения файла и отправки его клиенту. ¨ Как я могу это решить?

Изменить: я забыл упомянуть, что файлы находятся во временных каталогах.

Ответ 1

Если вы используете 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), чтобы запускать каждый час или около того и удалять файлы во временном каталоге, которые были созданы более чем за полчаса до этого.

Ответ 2

У меня есть два решения.


Первое решение - удалить файл в методе __exit__, но не закрыть его. Таким образом, файл-объект все еще доступен, и вы можете передать его в send_file.

Это будет работать, только если вы не используете X-Sendfile, потому что он использует имя файла.


Второе решение - полагаться на сборщик мусора. Вы можете передать send_file файл-объект, который очистит файл при удалении (метод __del__). Таким образом, файл удаляется только тогда, когда файл-объект удаляется из python. Вы можете использовать TemporaryFile для этого, если вы этого еще не сделали.

Ответ 3

Метод, который я использовал, - использовать слабые ссылки для удаления файла после завершения ответа.

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, которые я использовал.

Ответ 4

Немного поздно, но это то, что я использовал с помощью предложений 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