aboutsummaryrefslogtreecommitdiff
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%}