about summary refs log tree commit diff
path: root/uploader
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2025-06-10 15:54:27 -0500
committerFrederick Muriuki Muriithi2025-06-10 17:19:43 -0500
commit3ea81d0fbc5b93295f291315c1d05fe7a1911948 (patch)
tree2080eed9e38e484889854375bccdb9cc55ca80a8 /uploader
parentcda652080ebfc18915875e7422f1e128bab3c7c5 (diff)
downloadgn-uploader-3ea81d0fbc5b93295f291315c1d05fe7a1911948.tar.gz
Provide generalised success and error handling for the jobs.
Diffstat (limited to 'uploader')
-rw-r--r--uploader/background_jobs.py107
-rw-r--r--uploader/templates/jobs/job-error.html17
2 files changed, 111 insertions, 13 deletions
diff --git a/uploader/background_jobs.py b/uploader/background_jobs.py
index ac47ff2..09ea0c0 100644
--- a/uploader/background_jobs.py
+++ b/uploader/background_jobs.py
@@ -1,14 +1,86 @@
 import uuid
+import logging
+import importlib
+from typing import Callable
+from functools import partial
 
-from flask import request, Blueprint, render_template, current_app as app
+from flask import (
+    request,
+    url_for,
+    redirect,
+    Response,
+    Blueprint,
+    render_template,
+    current_app as app)
 
 from gn_libs import jobs
-from gn_libs.jobs.jobs import JobNotFound
 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,
+        error_handler: HandlerType = __default_error_handler__
+) -> str:
+    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}"
+        function = _parts[-1]
+        module = importlib.import_module(f".{_parts[-2]}",
+                                         package=".".join(_parts[0:-2]))
+        return getattr(module, _parts[-1])
+
+    metadata = job["metadata"]
+    if metadata["success_handler"]:
+        success_handler = __load_handler__(metadata["success_handler"])
+        try:
+            error_handler = __load_handler__(metadata["error_handler"])
+        except Exception as _exc:
+            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)
+    raise Exception(
+        f"No '{handler_type}' handler registered for job type: {_job_type}")
+
+
+error_handler = partial(handler, handler_type="error")
+success_handler = partial(handler, handler_type="success")
+
 
 @background_jobs_bp.route("/status/<uuid:job_id>")
 @require_login
@@ -17,19 +89,28 @@ def job_status(job_id: uuid.UUID):
     with sqlite3.connection(app.config["ASYNCHRONOUS_JOBS_SQLITE_DB"]) as conn:
         try:
             job = jobs.job(conn, job_id, fulldetails=True)
-            stdout = ""
-            stderr = ""
-            # with (open(job["metadata"]["stdout-file"], encoding="utf-8") as stdout_file,
-            #       open(job["metadata"]["stderr-file"], encoding="utf-8") as stderr_file):
-            #     stdout = stdout_file.read()
-            #     stderr = stderr_file.read()
+            status = job["metadata"]["status"]
 
-            return render_template(
-                f"jobs/job-status.html",
-                job=job,
-                stdout=stdout,
-                stderr=stderr)
+            register_job_handlers(job)
+            if status == "error":
+                return error_handler(job)
+
+            if status == "completed":
+                return success_handler(job)
+
+            return render_template(f"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):
+    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)
diff --git a/uploader/templates/jobs/job-error.html b/uploader/templates/jobs/job-error.html
new file mode 100644
index 0000000..b3015fc
--- /dev/null
+++ b/uploader/templates/jobs/job-error.html
@@ -0,0 +1,17 @@
+{%extends "base.html"%}
+
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Background Jobs: Error{%endblock%}
+
+{%block pagetitle%}Background Jobs: Error{%endblock%}
+
+{%block contents%}
+
+<h1>Background Jobs: Error</h1>
+<p>Job <strong>{{job["job_id"]}}</strong> failed!</p>
+<p>The error details are in the "STDERR" section below.</p>
+
+<h2>STDERR</h2>
+<pre>{{job["stderr"]}}</pre>
+{%endblock%}