Я некоторое время гуглил и не мог найти способ сделать это. У меня есть простое приложение Flask, которое берет CSV-файл, считывает его в файл данных Pandas, преобразует его и выводит как новый CSV-файл. Мне удалось загрузить и преобразовать его успешно с HTML
<div class="container">
<form method="POST" action="/convert" enctype="multipart/form-data">
<div class="form-group">
<br />
<input type="file" name="file">
<input type="submit" name="upload"/>
</div>
</form>
</div>
, где после того, как я нажимаю на кнопку отправить, он некоторое время запускает преобразование в фоновом режиме и автоматически запускает загрузку, как только она будет завершена. Код, который берет result_df и запускает загрузку, выглядит как
@app.route('/convert', methods=["POST"])
def convert(
if request.method == 'POST':
# Read uploaded file to df
input_csv_f = request.files['file']
input_df = pd.read_csv(input_csv_f)
# TODO: Add progress bar for pd_convert
result_df = pd_convert(input_df)
if result_df is not None:
resp = make_response(result_df.to_csv())
resp.headers["Content-Disposition"] = "attachment; filename=export.csv"
resp.headers["Content-Type"] = "text/csv"
return resp
Я бы хотел добавить индикатор выполнения к pd_convert
, который по сути является операцией применения панд. Я обнаружил, что tqdm
теперь работает с пандами и имеет progress_apply
метод вместо apply
. Но я не уверен, имеет ли это отношение к созданию индикатора на веб-странице. Думаю, так и должно быть, поскольку он работает на ноутбуках Jupyter. Как мне добавить индикатор выполнения для pd_convert()
здесь?
Конечный результат, который я хочу получить:
- Пользователь нажимает кнопку загрузки, выбирает файл CSV из своей файловой системы
- Клики пользователей отправляют
- Запускается индикатор выполнения
- Когда индикатор выполнения достигает 100%, запускается загрузка
1 и 2 сделаны. Тогда следующий вопрос - как запустить загрузку. На данный момент моя функция convert
запускает загрузку без проблем, потому что ответ формируется из файла. Если я хочу отобразить страницу, я формирую ответ с return render_template(...)
. Поскольку у меня может быть только один ответ, возможно ли иметь 3 и 4 только с одним вызовом на /convert
?
Не веб-разработчик, все еще изучающий основы. Заранее спасибо!
==== EDIT ====
Я попробовал пример здесь с некоторыми изменениями. Я получаю прогресс из индекса строки в цикле for на фрейме данных и помещаю его в Redis. Клиент получает информацию о прогрессе от Redis из потока, запрашивая эту новую конечную точку /progress
. Что-то вроде
@app.route('/progress')
def progress():
"""Get percentage progress for the dataframe process"""
r = redis.StrictRedis(
host=redis_host, port=redis_port, password=redis_password, decode_responses=True)
r.set("progress", str(0))
# TODO: Problem, 2nd submit doesn't clear progress to 0%. How to make independent progress for each client and clear to 0% on each submit
def get_progress():
p = int(r.get("progress"))
while p <= 100:
p = int(r.get("progress"))
p_msg = "data:" + str(p) + "\n\n"
yield p_msg
logging.info(p_msg)
if p == 100:
r.set("progress", str(0))
time.sleep(1)
return Response(get_progress(), mimetype='text/event-stream')
В настоящее время работает, но с некоторыми проблемами. Причиной, безусловно, является мое непонимание этого решения.
Вопросы:
- Мне нужно, чтобы прогресс сбрасывался на 0 при каждом нажатии кнопки
submit
. Я пробовал несколько мест, чтобы сбросить его до 0, но еще не нашел рабочую версию. Это определенно связано с моим непониманием того, как работает поток. Теперь он сбрасывается только при обновлении страницы.
- Как обрабатывать параллельные запросы, такие как состояние гонки Redis? Если несколько пользователей делают запросы одновременно, ход выполнения должен быть независимым для каждого из них. Я подумываю дать случайное
job_id
для каждого submit
события и сделать его ключом в Redis. Поскольку мне не нужна запись после каждой работы, я просто удалю запись после того, как она будет сделана.
Я чувствую, что моя недостающая часть - это понимание text/event-stream
. Ощущение, что я близок к рабочему решению. Пожалуйста, поделитесь своим мнением о том, что такое «правильный» способ сделать это. Я просто угадываю и пытаюсь собрать что-то, что работает с моим очень ограниченным пониманием.