Flask App:功能运行时更新进度条 [英] Flask App: Update progress bar while function runs

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

问题描述

我在Flask中构建了一个相当简单的WebApp,通过网站的API执行功能。我的用户使用他们的帐户URL和API令牌填写表单;当他们提交表单我有一个Python脚本,通过API从他们的帐户导出PDF文件。这个函数可能需要很长时间,所以我想在窗体页面上显示一个引导进度条,表明脚本在进程中有多远。我的问题是如何更新进度栏作为功能正在运行?这是我所说的简化版。



views.py:

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

export_pdf.html:

  {%extendsbase.html%} 

{%block content%}
{%include'flash.html'%}
< div class =well well-sm>
< h3>汇出PDF< / h3>
< form class =navbar-form navbar-leftaction =method =postname =receipt>
{{form.hidden_​​tag()}}
< br>
< div class =control-group {%if form.errors.account_url%} error {%endif%}>
< label classcontrol-labelfor =account_url>输入帐户网址:< / label>
< div class =controls>
{{form.account_url(size = 50,class =span4)}}
{%for form.errors.account_url%}
< span class =help-内联> [{{错误}}]< /跨度><峰; br>
{%endfor%}
< / div>
< / div>
< br>
< div class =control-group {%if form.errors.api_token%} error {%endif%}>
< label classcontrol-labelfor =api_token>输入API令牌:< / label>
< div class =controls>
{{form.api_token(size = 50,class =span4)}}
{%for form.errors.api_token%}
< span class =help-内联> [{{错误}}]< /跨度><峰; br>
{%endfor%}
< / div>
< / div>
< br>
< button type =submitclass =btn btn-primary btn-lg>提交< / button>
< br>
< br>
< div class =progress progress-striped active>

< span class =sr-only>< / span>
< / div>
< / form>
< / div>
< / div>
{%endblock%}

和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
对于PDF格式的PDF文件:
total = total + 1
pdf格式:pdf格式, ('%s.pdf'%(pdf.number),'wb')作为f:
f.write(body)
count = count + 1
if count%50 == 0:
time.sleep(1)

在最后一个函数中,的PDF我将出口,并有一个持续的计数,而它正在处理。如何将当前进度发送到我的.html文件以适应进度条的style =标签?最好以我可以在其他页面上重复使用相同工具进度条的方式。让我知道,如果我没有提供足够的信息。

解决方案

正如其他人在评论中所建议的,最简单的解决方案是在另一个线程中运行你的导出函数,让你的客户端用另一个请求来获取进度信息。
有多种方法来处理这个特定的任务。
根据您的需要,您可能会选择一个或多或少复杂的一个。



这是一个非常非常简单的例子, :

  import random 
导入线程
导入时间

从flask导入Flask


class ExportingThread(threading.Thread):
def __init __(self):
self.progress = 0
super().__ init __ )

def run(self):
#你输出的东西在这里...
在_范围内(10):
time.sleep(1)
self.progress + = 10


exporting_threads = {}
app = Flask(__ name__)
app.debug = True


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

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

return'task id:#%s'%thread_i d

$ b $ app_route('/ progress /< int:thread_id>')
def progress(thread_id):
global exporting_threads

return str(exporting_threads [thread_id] .progress)

$ b if if __name__ =='__main__':
app.run()
导出线程在每次认为合适的时候更新它的进度值。

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

pre $函数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)
}
$)b




如前所述,是非常简约的,你应该去一个稍微复杂的方法。
通常,我们会将特定线程的进度存储在数据库或某种缓存中,这样我们就不会依赖共享结构,从而避免了我的示例所带来的大部分内存和并发问题。

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 App:功能运行时更新进度条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆