Удалить загруженный файл после загрузки его с Flask

В настоящее время я работаю над небольшим веб-интерфейсом, который позволяет другим пользователям загружать файлы, конвертировать файлы, которые они загружают, и загружать конвертированные файлы. Детали преобразования не важны для моего вопроса.

В настоящее время я использую флай-закачки для управления загруженными файлами, и я храню их в файловой системе. Когда пользователь загружает и конвертирует файл, есть всевозможные красивые кнопки для удаления файла, поэтому папка uploads не заполняется.

Я не думаю, что это идеально. Я действительно хочу, чтобы файлы были удалены сразу после их загрузки. Я соглашусь на удаление файлов при завершении сеанса.

Я потратил некоторое время, пытаясь понять, как это сделать, но мне еще предстоит преуспеть. Это не похоже на необычную проблему, поэтому я считаю, что там должно быть какое-то решение, которое мне не хватает. У кого-нибудь есть решение?

Ответ 1

Есть несколько способов сделать это.

send_file а затем сразу же удалить (только для Linux)

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

Убирайся по таймеру

Запустите другой процесс по таймеру (возможно, с использованием cron) или используйте внутрипроцессный планировщик, такой как APScheduler, и очистите файлы, которые были на диске, во временном местоположении за пределами вашего времени ожидания (например, полчаса, одна неделя, тридцать дней, после того, как они были помечены как "загруженные" в RDMBS)

Это самый надежный способ, но он требует дополнительной сложности (cron, планировщик в процессе, рабочая очередь и т.д.)