Flaskで、ウェブサイトのAPIを介して機能を実行します。ユーザーがフォームにアカウントのURLとAPIトークンを入力します。フォームを送信すると、= python APIを使用してアカウントからPDFをエクスポートするスクリプト。この関数は時間がかかる可能性があるため、フォームページにbootstrapプログレスバーを表示して、スクリプトがプロセスのどこまで進んでいるか、関数の実行中に進行状況バーを更新するにはどうすればよいですか?これは、私が話していることの簡単なバージョンです。
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 %}
および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)
最後の関数では、エクスポートするPDFの合計数を取得し、処理中のカウントを継続しています。現在の進行状況を.htmlファイルに送信して、進行状況バーの 'style ='タグ内に収めるにはどうすればよいですか?できれば、他のページのプログレスバーに同じツールを再利用できる方法で。十分な情報を提供していない場合はお知らせください。
他の人がコメントで示唆したように、最も簡単な解決策は、エクスポート関数を別のスレッドで実行し、クライアントに別のリクエストで進行状況情報をプルさせることです。この特定のタスクを処理するには、複数のアプローチがあります。ニーズに応じて、多かれ少なかれ洗練されたものを選ぶかもしれません。
スレッドでそれを行う方法の非常に(非常に)最小限の例を次に示します。
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()
インデックスルート(/)で、エクスポートタスクごとにスレッドを生成し、クライアントが進捗ルート(/ progress/[exporting_thread])で後でそれを取得できるように、そのタスクにIDを返します。エクスポートスレッドは、適切であると考えるたびに進行状況の値を更新します。
クライアント側では、次のようになります(この例では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)
}
})
}
}
言ったように、この例は非常にミニマルであり、おそらくもう少し洗練されたアプローチをとるべきです。通常、特定のスレッドの進行状況をデータベースまたはなんらかのキャッシュに保存するので、共有構造に依存しないため、私の例のほとんどのメモリと同時実行性の問題を回避できます。
Redis( https://redis.io )は、通常、この種のタスクに適したメモリ内データベースストアです。 Python( https://pypi.python.org/pypi/redis )とうまく統合されます。
私はこのシンプルですが教育的なFlask SSE localhostでの実装を実行します。GAEでサードパーティ(ユーザーがアップロードした)ライブラリを処理するには:
lib
という名前のディレクトリを作成します。gevent
ライブラリディレクトリをlib
ディレクトリにコピーします。これらの行を_main.py
_に追加します。
_import sys
sys.path.insert(0,'lib')
_
それで全部です。子フォルダーからlib
ディレクトリーを使用する場合は、相対参照を使用します:sys.path.insert(0, ../../blablabla/lib')
から http://flask.pocoo.org/snippets/116/
_# author: [email protected]
#
# Make sure your gevent version is >= 1.0
import gevent
from gevent.wsgi import WSGIServer
from gevent.queue import Queue
from flask import Flask, Response
import time
# SSE "protocol" is described here: http://mzl.la/UPFyxY
class ServerSentEvent(object):
def __init__(self, data):
self.data = data
self.event = None
self.id = None
self.desc_map = {
self.data : "data",
self.event : "event",
self.id : "id"
}
def encode(self):
if not self.data:
return ""
lines = ["%s: %s" % (v, k)
for k, v in self.desc_map.iteritems() if k]
return "%s\n\n" % "\n".join(lines)
app = Flask(__name__)
subscriptions = []
# Client code consumes like this.
@app.route("/")
def index():
debug_template = """
<html>
<head>
</head>
<body>
<h1>Server sent events</h1>
<div id="event"></div>
<script type="text/javascript">
var eventOutputContainer = document.getElementById("event");
var evtSrc = new EventSource("/subscribe");
evtSrc.onmessage = function(e) {
console.log(e.data);
eventOutputContainer.innerHTML = e.data;
};
</script>
</body>
</html>
"""
return(debug_template)
@app.route("/debug")
def debug():
return "Currently %d subscriptions" % len(subscriptions)
@app.route("/publish")
def publish():
#Dummy data - pick up from request for real data
def notify():
msg = str(time.time())
for sub in subscriptions[:]:
sub.put(msg)
gevent.spawn(notify)
return "OK"
@app.route("/subscribe")
def subscribe():
def gen():
q = Queue()
subscriptions.append(q)
try:
while True:
result = q.get()
ev = ServerSentEvent(str(result))
yield ev.encode()
except GeneratorExit: # Or maybe use flask signals
subscriptions.remove(q)
return Response(gen(), mimetype="text/event-stream")
if __name__ == "__main__":
app.debug = True
server = WSGIServer(("", 5000), app)
server.serve_forever()
# Then visit http://localhost:5000 to subscribe
# and send messages by visiting http://localhost:5000/publish
_