diff options
author | Frederick Muriuki Muriithi | 2025-06-10 15:54:27 -0500 |
---|---|---|
committer | Frederick Muriuki Muriithi | 2025-06-10 17:19:43 -0500 |
commit | 3ea81d0fbc5b93295f291315c1d05fe7a1911948 (patch) | |
tree | 2080eed9e38e484889854375bccdb9cc55ca80a8 /uploader | |
parent | cda652080ebfc18915875e7422f1e128bab3c7c5 (diff) | |
download | gn-uploader-3ea81d0fbc5b93295f291315c1d05fe7a1911948.tar.gz |
Provide generalised success and error handling for the jobs.
Diffstat (limited to 'uploader')
-rw-r--r-- | uploader/background_jobs.py | 107 | ||||
-rw-r--r-- | uploader/templates/jobs/job-error.html | 17 |
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%} |