Flask 应用程序:在函数运行时更新进度条 [英] Flask App: Update progress bar while function runs

查看:22
本文介绍了Flask 应用程序:在函数运行时更新进度条的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在 Flask 中构建一个相当简单的 WebApp,它通过网站的 API 执行功能.我的用户使用他们的帐户 URL 和 API 令牌填写表单;当他们提交表单时,我有一个 python 脚本,可以通过 API 从他们的帐户导出 PDF.这个函数可能需要很长时间,所以我想在表单页面上显示一个引导程序进度条,指示脚本在进程中的进度.我的问题是如何在函数运行时更新进度条?这是我正在谈论的内容的简化版本.

views.py:

@app.route ('/export_pdf', methods = ['GET', 'POST'])def export_pdf():表单 = 导出PDF()如果 form.validate_on_submit():尝试:export_pdfs.main_program(form.account_url.data,form.api_token.data)flash ('导出的 PDF')返回重定向(url_for('export_pdf'))除了作为 e 的 TransportException:s = e.contentresult = re.search('(.*)', s)flash('出现身份验证错误:' + result.group(1))除了 FailedRequest 为 e:flash('出现错误:' + e.error)return render_template('export_pdf.html', title = 'Export PDFs', form = form)

export_pdf.html:

{% 扩展 "base.html" %}{% 块内容 %}{% 包含 'flash.html' %}<div class="well well-sm"><h3>导出 PDF</h3><form class="navbar-form navbar-left" action="" method ="post" name="receipt">{{form.hidden_​​tag()}}<br><div class="control-group{% if form.errors.account_url %} error{% endif %}"><label class"control-label" for="account_url">输入账户网址:</label><div class="控件">{{ form.account_url(size = 50, class = "span4")}}{% for error in form.errors.account_url %}<span class="help-inline">[{{error}}]</span><br>{% 结束为 %}

<br><div class="control-group{% if form.errors.api_token %} error{% endif %}"><label class"control-label" for="api_token">输入API Token:</label><div class="控件">{{ form.api_token(size = 50, class = "span4")}}{% for form.errors.api_token %}<span class="help-inline">[{{error}}]</span><br>{% 结束为 %}

<br><button type="submit" class="btn btn-primary btn-lg">提交</button><br><br><div class="progress progress-striped active"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"><span class="sr-only"></span>

</表单>

{% 结束块 %}

和 export_pdfs.py:

def main_program(url, token):api_caller = api.TokenClient(url, token)path = os.path.expanduser('~/Desktop/'+url+'_pdfs/')pdfs = list_all(api_caller.pdf.list, 'pdf')总计 = 0计数 = 1对于 pdf 中的 pdf:总计 = 总计 + 1对于 pdf 中的 pdf:标题,正文 = api_caller.getPDF(pdf_id=int(pdf.pdf_id))with open('%s.pdf' % (pdf.number), 'wb') as f:f.写(正文)计数 = 计数 + 1如果计数 % 50 == 0:时间.sleep(1)

在最后一个函数中,我有我将导出的 PDF 总数,并在处理过程中进行持续计数.如何将当前进度发送到我的 .html 文件以适应进度条的style="标签?最好是我可以在其他页面上为进度条重用相同的工具.如果我没有提供足够的信息,请告诉我.

解决方案

正如一些其他人在评论中所建议的,最简单的解决方案是在另一个线程中运行您的导出函数,并让您的客户端通过另一个请求拉取进度信息.有多种方法可以处理此特定任务.根据您的需要,您可以选择更复杂或更不复杂的.

这是一个关于如何使用线程进行操作的非常(非常)最小的示例:

随机导入进口螺纹导入时间从烧瓶进口烧瓶类导出线程(线程.线程):def __init__(self):self.progress = 0super().__init__()定义运行(自我):# 你导出的东西放在这里...对于 _ 范围(10):时间.sleep(1)自我进步 += 10exporting_threads = {}app = Flask(__name__)app.debug = True@app.route('/')定义索引():全局exporting_threadsthread_id = random.randint(0, 10000)exporting_threads[thread_id] = ExportingThread()exporting_threads[thread_id].start()返回任务 ID:#%s"% thread_id@app.route('/progress/')定义进度(thread_id):全局exporting_threads返回 str(exporting_threads[thread_id].progress)如果 __name__ == '__main__':应用程序运行()

在索引路由 (/) 中,我们为每个导出任务生成一个线程,并为该任务返回一个 ID,以便客户端稍后可以使用进度路由 (/progress/[exporting_thread]) 检索它.导出线程每次认为合适时都会更新其进度值.

在客户端,你会得到这样的东西(这个例子使用 jQuery):

function check_progress(task_id, progress_bar) {功能工人(){$.get('progress/' + task_id, function(data) {如果(进度<100){progress_bar.set_progress(progress)设置超时(工人,1000)}})}}

如前所述,此示例非常简约,您可能应该采用更复杂的方法.通常,我们会将特定线程的进度存储在数据库或某种缓存中,这样我们就不会依赖共享结构,从而避免我的示例中的大部分内存和并发问题.

Redis (https://redis.io) 是一种内存数据库存储,通常非常适合这种任务.它与 Python 完美集成(https://pypi.python.org/pypi/redis).

I'm building a fairly simple WebApp in Flask that performs functions via a website's API. My users fill out a form with their account URL and API token; when they submit the form I have a python script that exports PDFs from their account via the API. This function can take a long time so I want to display a bootstrap progress bar on the form page indicating how far along in the process the script is. My question is how to I update the progress bar as the function is running? Here is a simplified version of what I'm talking about.

views.py:

@app.route ('/export_pdf', methods = ['GET', 'POST'])
def export_pdf():
    form = ExportPDF()
    if form.validate_on_submit():
      try:
        export_pdfs.main_program(form.account_url.data,
          form.api_token.data)
        flash ('PDFs exported')
        return redirect(url_for('export_pdf'))
      except TransportException as e:
        s = e.content
        result = re.search('<error>(.*)</error>', s)
        flash('There was an authentication error: ' + result.group(1))
      except FailedRequest as e:
        flash('There was an error: ' + e.error)
    return render_template('export_pdf.html', title = 'Export PDFs', form = form)

export_pdf.html:

{% extends "base.html" %}

{% block content %}
{% include 'flash.html' %}
<div class="well well-sm">
  <h3>Export PDFs</h3>
  <form class="navbar-form navbar-left" action="" method ="post" name="receipt">
    {{form.hidden_tag()}}
    <br>
    <div class="control-group{% if form.errors.account_url %} error{% endif %}">
      <label class"control-label" for="account_url">Enter Account URL:</label>
      <div class="controls">
        {{ form.account_url(size = 50, class = "span4")}}
        {% for error in form.errors.account_url %}
          <span class="help-inline">[{{error}}]</span><br>
        {% endfor %}
      </div>
    </div>
    <br>
    <div class="control-group{% if form.errors.api_token %} error{% endif %}">
      <label class"control-label" for="api_token">Enter API Token:</label>
      <div class="controls">
        {{ form.api_token(size = 50, class = "span4")}}
        {% for error in form.errors.api_token %}
          <span class="help-inline">[{{error}}]</span><br>
        {% endfor %}
      </div>
    </div>
    <br>
    <button type="submit" class="btn btn-primary btn-lg">Submit</button>
  <br>
  <br>
  <div class="progress progress-striped active">
  <div class="progress-bar"  role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
    <span class="sr-only"></span>
  </div>
</form>
</div>
</div>
{% endblock %}

and export_pdfs.py:

def main_program(url, token):
    api_caller = api.TokenClient(url, token)
    path = os.path.expanduser('~/Desktop/'+url+'_pdfs/')
    pdfs = list_all(api_caller.pdf.list, 'pdf')
    total = 0
    count = 1
    for pdf in pdfs:
        total = total + 1
    for pdf in pdfs:
        header, body = api_caller.getPDF(pdf_id=int(pdf.pdf_id))
        with open('%s.pdf' % (pdf.number), 'wb') as f:
          f.write(body)
        count = count + 1
        if count % 50 == 0:
          time.sleep(1)

In that last function I have total the number of PDFs I will export, and have an ongoing count while it is processing. How can I send the current progress to my .html file to fit within the 'style=' tag of the progress bar? Preferably in a way that I can reuse the same tool for progress bars on other pages. Let me know if I haven't provided enough info.

解决方案

As some others suggested in the comments, the simplest solution is to run your exporting function in another thread, and let your client pull progress information with another request. There are multiple approaches to handle this particular task. Depending on your needs, you might opt for a more or less sophisticated one.

Here's a very (very) minimal example on how to do it with threads:

import random
import threading
import time

from flask import Flask


class ExportingThread(threading.Thread):
    def __init__(self):
        self.progress = 0
        super().__init__()

    def run(self):
        # Your exporting stuff goes here ...
        for _ in range(10):
            time.sleep(1)
            self.progress += 10


exporting_threads = {}
app = Flask(__name__)
app.debug = True


@app.route('/')
def index():
    global exporting_threads

    thread_id = random.randint(0, 10000)
    exporting_threads[thread_id] = ExportingThread()
    exporting_threads[thread_id].start()

    return 'task id: #%s' % thread_id


@app.route('/progress/<int:thread_id>')
def progress(thread_id):
    global exporting_threads

    return str(exporting_threads[thread_id].progress)


if __name__ == '__main__':
    app.run()

In the index route (/) we spawn a thread for each exporting task, and we return an ID to that task so that the client can retrieve it later with the progress route (/progress/[exporting_thread]). The exporting thread updates its progress value every time it thinks it is appropriate.

On the client side, you would get something like this (this example uses jQuery):

function check_progress(task_id, progress_bar) {
    function worker() {
        $.get('progress/' + task_id, function(data) {
            if (progress < 100) {
                progress_bar.set_progress(progress)
                setTimeout(worker, 1000)
            }
        })
    }
}

As said, this example is very minimalistic and you should probably go for a slightly more sophisticated approach. Usually, we would store the progress of a particular thread in a database or a cache of some sort, so that we don't rely on a shared structure, hence avoiding most of the memory and concurrency issues my example has.

Redis (https://redis.io) is an in-memory database store that is generally well-suited for this kind of tasks. It integrates ver nicely with Python (https://pypi.python.org/pypi/redis).

这篇关于Flask 应用程序:在函数运行时更新进度条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
Python最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆