about summary refs log tree commit diff
path: root/uploader/background_jobs.py
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/background_jobs.py')
-rw-r--r--uploader/background_jobs.py118
1 files changed, 118 insertions, 0 deletions
diff --git a/uploader/background_jobs.py b/uploader/background_jobs.py
new file mode 100644
index 0000000..d33c498
--- /dev/null
+++ b/uploader/background_jobs.py
@@ -0,0 +1,118 @@
+"""Generic views and utilities to handle background jobs."""
+import uuid
+import importlib
+from typing import Callable
+from functools import partial
+
+from flask import (
+    url_for,
+    redirect,
+    Response,
+    Blueprint,
+    render_template,
+    current_app as app)
+
+from gn_libs import jobs
+from gn_libs import sqlite3
+from gn_libs.jobs.jobs import JobNotFound
+
+from uploader.authorisation import require_login
+
+background_jobs_bp = Blueprint("background-jobs", __name__)
+HandlerType = Callable[[dict], Response]
+
+
+def __default_error_handler__(job: dict) -> Response:
+    return redirect(url_for("background-jobs.job_error", job_id=job["job_id"]))
+
+def register_handlers(
+        job_type: str,
+        success_handler: HandlerType,
+        # pylint: disable=[redefined-outer-name]
+        error_handler: HandlerType = __default_error_handler__
+        # pylint: disable=[redefined-outer-name]
+) -> str:
+    """Register success and error handlers for each job type."""
+    if not bool(app.config.get("background-jobs")):
+        app.config["background-jobs"] = {}
+
+    if not bool(app.config["background-jobs"].get(job_type)):
+        app.config["background-jobs"][job_type] = {
+            "success": success_handler,
+            "error": error_handler
+        }
+
+    return job_type
+
+
+def register_job_handlers(job: str):
+    """Related to register handlers above."""
+    def __load_handler__(absolute_function_path):
+        _parts = absolute_function_path.split(".")
+        app.logger.debug("THE PARTS ARE: %s", _parts)
+        assert len(_parts) > 1, f"Invalid path: {absolute_function_path}"
+        module = importlib.import_module(f".{_parts[-2]}",
+                                         package=".".join(_parts[0:-2]))
+        return getattr(module, _parts[-1])
+
+    metadata = job["metadata"]
+    if metadata.get("success_handler"):
+        _success_handler = __load_handler__(metadata["success_handler"])
+        try:
+            _error_handler = __load_handler__(metadata["error_handler"])
+        except Exception as _exc:# pylint: disable=[broad-exception-caught]
+            _error_handler = __default_error_handler__
+        register_handlers(
+            metadata["job-type"], _success_handler, _error_handler)
+
+
+def handler(job: dict, handler_type: str) -> HandlerType:
+    """Fetch a handler for the job."""
+    _job_type = job["metadata"]["job-type"]
+    _handler = app.config.get(
+        "background-jobs", {}
+    ).get(
+        _job_type, {}
+    ).get(handler_type)
+    if bool(_handler):
+        return _handler(job)
+    return render_template("background-jobs/default-success-page.html", job=job)
+
+
+error_handler = partial(handler, handler_type="error")
+success_handler = partial(handler, handler_type="success")
+
+
+@background_jobs_bp.route("/status/<uuid:job_id>")
+@require_login
+def job_status(job_id: uuid.UUID):
+    """View the job status."""
+    with sqlite3.connection(app.config["ASYNCHRONOUS_JOBS_SQLITE_DB"]) as conn:
+        try:
+            job = jobs.job(conn, job_id, fulldetails=True)
+            status = job["metadata"]["status"]
+
+            register_job_handlers(job)
+            if status == "error":
+                return error_handler(job)
+
+            if status == "completed":
+                return success_handler(job)
+
+            return render_template("jobs/job-status.html", job=job)
+        except JobNotFound as _jnf:
+            return render_template(
+                "jobs/job-not-found.html",
+                job_id=job_id)
+
+
+@background_jobs_bp.route("/error/<uuid:job_id>")
+@require_login
+def job_error(job_id: uuid.UUID):
+    """Handle job errors in a generic manner."""
+    with sqlite3.connection(app.config["ASYNCHRONOUS_JOBS_SQLITE_DB"]) as conn:
+        try:
+            job = jobs.job(conn, job_id, fulldetails=True)
+            return render_template("jobs/job-error.html", job=job)
+        except JobNotFound as _jnf:
+            return render_template("jobs/job-not-found.html", job_id=job_id)