about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2022-10-28 11:51:50 +0300
committerFrederick Muriuki Muriithi2022-10-28 15:58:01 +0300
commit0bb49f9873e958cd480f7c9fc20f3a8db6f62003 (patch)
tree1e13625ddb9b581ad16db98ab75619ef1fdacdce
parent2357452ccdcf475bcad3a5da7de43f4d481e296b (diff)
downloadgenenetwork2-0bb49f9873e958cd480f7c9fc20f3a8db6f62003.tar.gz
Refactor: run correlation computation externally
* wqflask/scripts/corr_compute.py: move correlation computation to
  external script.
* wqflask/wqflask/templates/loading_corrs.html: Provide UI to display
  while computation is still not complete.
* wqflask/wqflask/views.py: Dispatch correlation computation to
  external script and display appropriate UI for each valid state of
  the computation.
-rw-r--r--wqflask/scripts/corr_compute.py78
-rw-r--r--wqflask/wqflask/templates/loading_corrs.html28
-rw-r--r--wqflask/wqflask/views.py59
3 files changed, 148 insertions, 17 deletions
diff --git a/wqflask/scripts/corr_compute.py b/wqflask/scripts/corr_compute.py
new file mode 100644
index 00000000..c98a66a4
--- /dev/null
+++ b/wqflask/scripts/corr_compute.py
@@ -0,0 +1,78 @@
+"""Compute the correlations."""
+
+import sys
+import json
+import pickle
+import pathlib
+import datetime
+
+from flask import g
+
+from wqflask import app
+from wqflask.user_session import UserSession
+from wqflask.correlation.show_corr_results import set_template_vars
+from wqflask.correlation.correlation_gn3_api import compute_correlation
+
+class UserSessionSimulator():
+
+    def __init__(self, user_id):
+        self._user_id = user_id
+
+    @property
+    def user_id(self):
+        return self._user_id
+
+error_types = {
+    "WrongCorrelationType": "Wrong Correlation Type",
+    "CalledProcessError": "Called Process Error"
+}
+
+def e_time():
+    return datetime.datetime.utcnow().isoformat()
+
+def compute(form):
+    import subprocess
+    from gn3.settings import CORRELATION_COMMAND
+    try:
+        correlation_results = compute_correlation(form, compute_all=True)
+    except Exception as exc:
+        return {
+            "error-type": error_types[type(exc).__name__],
+            "error-message": exc.args[0]
+        }
+
+    return set_template_vars(form, correlation_results)
+
+if __name__ == "__main__":
+    ARGS_COUNT = 3
+    if len(sys.argv) < ARGS_COUNT:
+        print(f"{e_time()}: You need to pass the file with the picked form",
+              file=sys.stderr)
+        sys.exit(1)
+
+    if len(sys.argv) > ARGS_COUNT:
+        print(f"{e_time()}: Unknown arguments {sys.argv[ARGS_COUNT:]}",
+              file=sys.stderr)
+        sys.exit(1)
+
+    filepath = pathlib.Path(sys.argv[1])
+    if not filepath.exists():
+        print(f"File not found '{filepath}'", file=sys.stderr)
+        sys.exit(2)
+
+    with open(filepath, "rb") as pfile:
+        form = pickle.Unpickler(pfile).load()
+
+    with app.app_context():
+        g.user_session = UserSessionSimulator(sys.argv[2])
+        results = compute(form)
+
+    print(json.dumps(results), file=sys.stdout)
+
+    if "error-type" in results:
+        print(
+            f"{results['error-type']}: {results['error-message']}",
+            file=sys.stderr)
+        sys.exit(3)
+
+    sys.exit(0)
diff --git a/wqflask/wqflask/templates/loading_corrs.html b/wqflask/wqflask/templates/loading_corrs.html
new file mode 100644
index 00000000..8abd5464
--- /dev/null
+++ b/wqflask/wqflask/templates/loading_corrs.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+
+  <head>
+    <title>Loading Correlation Results</title>
+
+    <meta charset="utf-8" />
+    <meta http-equiv="refresh" content="5">
+
+    <link rel="stylesheet" type="text/css"
+	  href="{{url_for('css', filename='bootstrap/css/bootstrap.css')}}" />
+    <link rel="stylesheet" type="text/css"
+	  href="/static/new/css/bootstrap-custom.css" />
+  </head>
+
+  <body>
+    <div style="margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
+      <h1>&nbsp;Correlation Computation in progress ...</h1>
+      <div style="text-align: center;">
+	<img align="center" src="/static/gif/waitAnima2.gif">
+      </div>
+    </div>
+
+    <script src="{{ url_for('js', filename='jquery/jquery.min.js') }}" type="text/javascript"></script>
+    <script src="{{ url_for('js', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
+  </body>
+
+</html>
diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py
index 0d8e5e64..211a8f13 100644
--- a/wqflask/wqflask/views.py
+++ b/wqflask/wqflask/views.py
@@ -19,6 +19,8 @@ import xlsxwriter
 from zipfile import ZipFile
 from zipfile import ZIP_DEFLATED
 
+from uuid import UUID
+
 from wqflask import app
 
 from gn3.computations.gemma import generate_hash_of_string
@@ -31,6 +33,7 @@ from flask import render_template
 from flask import send_from_directory
 from flask import redirect
 from flask import send_file
+from flask import url_for
 
 # Some of these (like collect) might contain endpoints, so they're still used.
 # Blueprints should probably be used instead.
@@ -71,6 +74,7 @@ from wqflask.db_info import InfoPage
 from utility import temp_data
 from utility.tools import TEMPDIR
 from utility.tools import USE_REDIS
+from utility.tools import REDIS_URL
 from utility.tools import GN_SERVER_URL
 from utility.tools import GN_VERSION
 from utility.tools import JS_TWITTER_POST_FETCHER_PATH
@@ -78,11 +82,16 @@ from utility.tools import JS_GUIX_PATH
 from utility.helper_functions import get_species_groups
 from utility.redis_tools import get_redis_conn
 
+import utility.hmac as hmac
+
 
+from base.webqtlConfig import TMPDIR
 from base.webqtlConfig import GENERATED_IMAGE_DIR
 
 from wqflask.database import database_connection
 
+import jobs.jobs as jobs
+
 
 Redis = get_redis_conn()
 
@@ -822,24 +831,40 @@ def __handle_correlation_error__(exc):
             "error-message": exc.args[0]
         })
 
-@app.route("/corr_compute", methods=('POST',))
+@app.route("/corr_compute", methods=('POST', 'GET'))
 def corr_compute_page():
-    import subprocess
-    from gn3.settings import CORRELATION_COMMAND
-    try:
-        correlation_results = compute_correlation(
-            request.form, compute_all=True)
-    except WrongCorrelationType as exc:
-        return __handle_correlation_error__(exc)
-    except subprocess.CalledProcessError as cpe:
-        actual_command = (
-            os.readlink(CORRELATION_COMMAND)
-            if os.path.islink(CORRELATION_COMMAND)
-            else CORRELATION_COMMAND)
-        raise Exception(actual_command, cpe.output, cpe.stdout, cpe.stderr) from cpe
-
-    correlation_results = set_template_vars(request.form, correlation_results)
-    return render_template("correlation_page.html", **correlation_results)
+    with Redis.from_url(REDIS_URL, decode_responses=True) as rconn:
+        if request.method == "POST":
+            request_received = datetime.datetime.utcnow()
+            filename=hmac.hmac_creation(f"request_form_{request_received.isoformat()}")
+            filepath = f"{TMPDIR}{filename}"
+            with open(filepath, "wb") as pfile:
+                pickle.dump(request.form, pfile, protocol=pickle.HIGHEST_PROTOCOL)
+                job_id = jobs.queue(
+                    rconn, {
+                        "command": [
+                            sys.executable, "-m", "scripts.corr_compute", filepath,
+                            g.user_session.user_id],
+                        "request_received_time": request_received.isoformat(),
+                        "status": "queued"
+                    })
+                jobs.run(job_id, REDIS_URL)
+
+            return redirect(url_for("corr_compute_page", job_id=str(job_id)))
+
+        job = jobs.job(
+            rconn, UUID(request.args.get("job_id"))).maybe(
+                {}, lambda the_job: the_job)
+
+        if jobs.completed_successfully(job):
+            output = json.loads(job.get("stdout", "{}"))
+            return render_template("correlation_page.html", **output)
+
+        if jobs.completed_erroneously(job):
+            output = json.loads(job.get("stdout", "{}"))
+            return render_template("correlation_error_page.html", error=output)
+
+        return render_template("loading_corrs.html")
 
 
 @app.route("/corr_matrix", methods=('POST',))