Commits

Chris Mutel committed e4f2ac4

0.2: Added ghetto jobs and progress test; use werkzeug directly as server for multithreading

  • Participants
  • Parent commits 6d14e56

Comments (0)

Files changed (6)

File bw2ui/bin/bw2-web.py

 """
 from bw2ui.web import bw2webapp
 from docopt import docopt
+from werkzeug.serving import run_simple
 import random
 import threading
 import webbrowser
 
 if __name__ == "__main__":
     args = docopt(__doc__, version='Brightway2 Web UI 0.1')
-
-    if args["--port"]:
-        port = int(args["--port"])
-    else:
-        port = 5000 + random.randint(0, 999)
-    url = "http://127.0.0.1:{}".format(port)
+    port = int(args.get("--port", False) or 5000 + random.randint(0, 999))
+    host = "0.0.0.0" if args.get("--insecure", False) else "localhost"
 
     if not args["--nobrowser"]:
+        url = "http://127.0.0.1:{}".format(port)
         threading.Timer(1., lambda: webbrowser.open_new_tab(url)).start()
 
     kwargs = {
-        "port": port,
-        "debug": args["--debug"]
+        "processes": args.get("--processes", 0) or 3,
+        "use_debugger": args["--debug"]
     }
-    if args["--insecure"]:
-        kwargs["host"] = '0.0.0.0'
 
-    bw2webapp.run(**kwargs)
+    run_simple(host, port, bw2webapp, **kwargs)

File bw2ui/web/app.py

 # -*- coding: utf-8 -*-
-import base64
 from brightway2 import config, databases, methods, Database, Method, \
     JsonWrapper
 from bw2analyzer import ContributionAnalysis
 from bw2calc import LCA
-from flask import Flask, url_for, render_template, request, redirect, abort
-import json
-import os
+from flask import Flask, url_for, render_template, request, redirect, abort, \
+    Response
+from jobs import JobDispatch, InvalidJob
+from utils import get_job_id, get_job, set_job_status
+import base64
 
 app = Flask(__name__)
 
     return render_template('500.html'), 500
 
 
+@app.route("/status/<job>")
+def job_status(job):
+    try:
+        return Response(JsonWrapper.dumps(get_job(job)),
+            mimetype='application/json')
+    except:
+        abort(404)
+
+
+@app.route("/dispatch/<job>")
+def job_dispatch(job):
+    try:
+        job_data = get_job(job)
+    except:
+        abort(404)
+    try:
+        return JobDispatch()(job, **job_data)
+    except InvalidJob:
+        abort(500)
+
+
 @app.route('/')
 def index():
     context = {
     return render_template("index.html", **context)
 
 
-@app.route('/error')
-def error_t():
-    abort(404)
-
-
 @app.route('/calculate/lca')
 @app.route('/calculate/lca/<process>/<method>')
 def lca(process=None, method=None):
         lca.lcia()
         rt, rb = lca.reverse_dict()
         ca = ContributionAnalysis()
-        context["treemap_data"] = json.dumps(ca.d3_treemap(
+        context["treemap_data"] = JsonWrapper.dumps(ca.d3_treemap(
             lca.characterized_inventory.data, rb, rt))
         context["ia_score"] = "%.2g" % lca.score
         context["ia_unit"] = methods[method]["unit"]
     else:
         return "No parameters"
 
-import uuid
 
-
-def get_job_id():
-    return uuid.uuid4().hex
+@app.route('/progress')
+def progress_test():
+    job_id = get_job_id()
+    status_id = get_job_id()
+    set_job_status(job_id, {"name": "progress-test", "status": status_id})
+    set_job_status(status_id, {"status": "Starting..."})
+    return render_template("progress.html", **{"job": job_id, 'status': status_id})
 
 
 @app.route('/start', methods=["GET", "POST"])
     # There is no step four!
     # Actually, there is: go to the normal homepage
 
-jobs_dir = config.request_dir("jobs")
-
-
-def set_job_status(job, status):
-    filepath = os.path.join(jobs_dir, "%s.json" % job)
-    JsonWrapper.dump(status, filepath)
-
 # Normal homepage:
 # Think of what should be here
 # At a minimum, a table of databases

File bw2ui/web/jobs.py

+from utils import get_job, set_job_status
+import time
+
+example_text = """There's little can be said in 't; 'tis against the
+rule of nature. To speak on the part of virginity,
+is to accuse your mothers; which is most infallible
+disobedience. He that hangs himself is a virgin:
+virginity murders itself and should be buried in
+highways out of all sanctified limit, as a desperate
+offendress against nature. Virginity breeds mites,
+much like a cheese; consumes itself to the very
+paring, and so dies with feeding his own stomach.
+Besides, virginity is peevish, proud, idle, made of
+self-love, which is the most inhibited sin in the
+canon. Keep it not; you cannot choose but loose
+by't: out with 't! within ten year it will make
+itself ten, which is a goodly increase; and the
+principal itself not much the worse: away with 't!""".split("\n")
+
+
+class InvalidJob(StandardError):
+    pass
+
+
+class JobDispatch(object):
+    """Super-ghetto asynchronous jobs framework: Return a web page, which then makes another request(s) to start asynchronous jobs."""
+    def __call__(self, job, **kwargs):
+        if kwargs.get("name", None) == "progress-test":
+            return progress_test(job, **kwargs)
+        else:
+            raise InvalidJob
+
+
+def progress_test(job, **kwargs):
+    time.sleep(0.5)
+    set_job_status(kwargs["status"], {"status": "Dispatched..."})
+    for x in example_text:
+        time.sleep(0.5)
+        set_job_status(kwargs["status"], {"status": x})
+    set_job_status(kwargs["status"], {"status": "Finished", "finished": True})
+    return "done"

File bw2ui/web/templates/progress.html

+{% extends "base.html" %}
+
+{% block extrahead %}
+<style type="text/css">
+.result {
+  margin-bottom: 0.25em;
+}
+</style>
+{% endblock %}
+
+{% block body %}
+<h1>Testing async dispatch</h1>
+<hr>
+<h2>Status</h2>
+<div class="push-4 span-16">
+  <div id="status-window"></div>
+</div>
+<script type="text/javascript">
+YUI().use('node', 'datasource-io', 'datasource-polling', 'json-parse', function (Y) {
+  var last_message = "",
+    container = Y.one("#status-window"),
+    dispatch = new Y.DataSource.IO({source: "/dispatch/{{job}}"});
+
+  job_status = new Y.DataSource.IO({source: "/status/{{status}}"});
+  polling = job_status.setInterval(250, {
+    request: "",
+    callback: {
+      success: function (e) {
+        console.log("Success");
+        console.log(e.response);
+        console.log(e.response.results[0]);
+        console.log(Y.JSON.parse(e.response.results[0].response));
+        var status_data = Y.JSON.parse(e.response.results[0].response);
+        if (status_data.status != last_message) {
+          last_message = status_data.status;
+          container.append("<p class=\"result\">" + last_message + "</p>");
+        };
+        if (status_data.finished === true) {
+          job_status.clearInterval(polling)
+        };
+      },
+      failure: function (e) {
+        console.log("Error");
+        console.log(e);
+      }
+    }
+  });
+
+  dispatch.sendRequest({
+    request: "",
+    callback: {
+      success: function() {},
+      failure: function() {}
+    }
+  });
+});
+</script>
+{% endblock %}

File bw2ui/web/utils.py

+from brightway2 import config, JsonWrapper
+import os
+import uuid
+
+jobs_dir = config.request_dir("jobs")
+
+
+def set_job_status(job, status):
+    JsonWrapper.dump(status, os.path.join(jobs_dir, "%s.json" % job))
+
+
+def get_job(job):
+    return JsonWrapper.load(os.path.join(jobs_dir, "%s.json" % job))
+
+
+def get_job_id():
+    return uuid.uuid4().hex
 
 setup(
   name='bw2ui',
-  version="0.1",
+  version="0.2",
   packages=["bw2ui", "bw2ui.web"],
   package_data={'bw2ui': ["web/static/*/*.*", "web/templates/*.*", "web/templates/*/*.*"]},
   author="Chris Mutel",