about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-02-15 08:01:03 +0300
committerFrederick Muriuki Muriithi2024-02-15 08:47:19 +0300
commit22a3076313b5ea3229320ba2ea30b77b3eb72c7e (patch)
tree43531254f0d4bf206cf19c6a7ebcfca2cc066051
parent8afa048a8dce9df6b03dfd5868c0dd0952eaec44 (diff)
downloadgn-uploader-22a3076313b5ea3229320ba2ea30b77b3eb72c7e.tar.gz
Refactor checking for user input errors.
-rw-r--r--qc_app/upload/rqtl2.py716
1 files changed, 387 insertions, 329 deletions
diff --git a/qc_app/upload/rqtl2.py b/qc_app/upload/rqtl2.py
index 41663ff..d42780f 100644
--- a/qc_app/upload/rqtl2.py
+++ b/qc_app/upload/rqtl2.py
@@ -1,12 +1,13 @@
 """Module to handle uploading of R/qtl2 bundles."""
 import sys
 import json
-from typing import Union
 from pathlib import Path
 from datetime import date
 from uuid import UUID, uuid4
 from zipfile import ZipFile, is_zipfile
+from typing import Union, Callable, Optional, Iterable
 
+import MySQLdb as mdb
 from redis import Redis
 from MySQLdb.cursors import DictCursor
 from flask import (
@@ -14,6 +15,7 @@ from flask import (
     request,
     url_for,
     redirect,
+    Response,
     Blueprint,
     render_template,
     current_app as app)
@@ -47,7 +49,7 @@ from qc_app.db.datasets import (
 rqtl2 = Blueprint("rqtl2", __name__)
 
 @rqtl2.route("/", methods=["GET", "POST"])
-@rqtl2.route("/select-species", methods=["POST"])
+@rqtl2.route("/select-species", methods=["GET", "POST"])
 def select_species():
     """Select the species."""
     if request.method == "GET":
@@ -249,94 +251,156 @@ def rqtl2_bundle_qc_status(jobid: UUID):
         except jobs.JobNotFound:
             return render_template("rqtl2/no-such-job.html", jobid=jobid)
 
-def check_errors(conn, *args, **kwargs):#pylint: disable=[too-many-return-statements]
-    """Check for select errors in the forms and return a page to redirect to."""
-    species_id = kwargs.get("species_id") or request.form.get("species_id")
-    population_id = (kwargs.get("population_id")
-                     or request.form.get("population_id"))
-    species = species_by_id(conn, species_id)
-    population = population_by_species_and_id(conn, species_id, population_id)
-
-    if "species" in args and not bool(species):
-        flash("Invalid species!", "alert-error error-rqtl2")
-        return redirect(url_for("upload.rqtl2.select_species"))
-
-    if "population" in args and not bool(population):
-        flash("Invalid Population!", "alert-error error-rqtl2")
-        return redirect(
-            url_for("upload.rqtl2.select_population", pgsrc="error"),
-            code=307)
-
-    if "rqtl2_bundle_file" in args:
-        fileuploadpage = redirect(url_for("upload.rqtl2.upload_rqtl2_bundle",
-                                          species_id=species_id,
-                                          population_id=population_id,
-                                          pgsrc="error"),
-                                  code=307)
-        if not bool(request.form.get("rqtl2_bundle_file")):
-            flash("There is no file to process.", "alert-error error-rqtl2")
-            return fileuploadpage
+def form_errors(formargs, *errorcheckers) -> Iterable[tuple[str, Response]]:
+    """Retrieve all errors from the form inputs"""
+    return (checker(formargs) for checker in errorcheckers)
 
-        if not Path(fullpath(request.form["rqtl2_bundle_file"])).exists():
-            flash("No R/qtl2 bundle with the given name exists.",
-                  "alert-error error-rqtl2")
-            return fileuploadpage
-
-    if ("geno-dataset" in args and
-        not bool(request.form.get("geno-dataset-id"))):
-        flash("No genotype dataset was provided!", "alert-error error-rqtl2")
-        return redirect(url_for("upload.rqtl2.select_geno_dataset",
-                                species_id=species_id,
-                                population_id=population_id,
-                                pgsrc="error"),
-                        code=307)
-
-    summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
-                                    species_id=species_id,
-                                    population_id=population_id,
-                                    pgsrc="error"),
-                            code=307)
+def redirect_on_error(flaskroute, **kwargs):
+    """Utility to redirect on error"""
+    return redirect(url_for(flaskroute, **kwargs, pgsrc="error"),
+                    code=(307 if request.method == "POST" else 302))
+
+def check_species(conn: mdb.Connection, formargs: dict) -> Optional[
+        tuple[str, Response]]:
+    """
+    Check whether the 'species_id' value is provided, and whether a
+    corresponding species exists in the database.
 
-    if ("probe-study-id" in args and
-        not bool(request.form.get("probe-study-id"))):
-        flash("No probeset study was selected!", "alert-error error-rqtl2")
-        return summary_page
+    Maybe give the function a better name..."""
+    speciespage = redirect_on_error("upload.rqtl2.select_species")
+    if "species_id" not in formargs:
+        return "You MUST provide the Species identifier.", speciespage
 
-    if ("probe-dataset-id" in args and
-        not bool(request.form.get("probe-dataset-id"))):
-        flash("No probeset dataset was selected!", "alert-error error-rqtl2")
-        return summary_page
+    if not bool(species_by_id(conn, formargs["species_id"])):
+        return "No species with the provided identifier exists.", speciespage
 
     return None
 
+def check_population(conn: mdb.Connection,
+                     formargs: dict,
+                     species_id) -> Optional[tuple[str, Response]]:
+    """
+    Check whether the 'population_id' value is provided, and whether a
+    corresponding population exists in the database.
+
+    Maybe give the function a better name..."""
+    poppage = redirect_on_error(
+        "upload.rqtl2.select_species", species_id=species_id)
+    if "population_id" not in formargs:
+        return "You MUST provide the Population identifier.", poppage
+
+    if not bool(population_by_species_and_id(
+            conn, species_id, formargs["population_id"])):
+        return "No population with the provided identifier exists.", poppage
+
+    return None
+
+def check_r_qtl2_bundle(formargs: dict,
+                        species_id,
+                        population_id) -> Optional[tuple[str, Response]]:
+    """Check for the existence of the R/qtl2 bundle."""
+    fileuploadpage = redirect_on_error("upload.rqtl2.upload_rqtl2_bundle",
+                                       species_id=species_id,
+                                       population_id=population_id)
+    if not "rqtl2_bundle_file" in formargs:
+        return (
+            "You MUST provide a R/qtl2 zip bundle for upload.", fileuploadpage)
+
+    if not Path(fullpath(formargs["rqtl2_bundle_file"])).exists():
+        return "No R/qtl2 bundle with the given name exists.", fileuploadpage
+
+    return None
+
+def check_geno_dataset(formargs: dict,
+                       species_id,
+                       population_id) -> Optional[tuple[str, Response]]:
+    """Check for the Genotype dataset."""
+    genodsetpg = redirect_on_error("upload.rqtl2.select_geno_dataset",
+                                   species_id=species_id,
+                                   population_id=population_id)
+    if not bool(formargs.get("geno-dataset-id")):
+        return (
+            "You MUST provide a valid Genotype dataset identifier", genodsetpg)
+
+    return None
+
+def check_probe_study(formargs: dict,
+                      species_id,
+                      population_id) -> Optional[tuple[str, Response]]:
+    """Check for the ProbeSet study."""
+    dsetinfopg = redirect_on_error("upload.rqtl2.select_dataset_info",
+                                   species_id=species_id,
+                                   population_id=population_id)
+    if not bool(formargs.get("probe-study-id")):
+        return "No probeset study was selected!", dsetinfopg
+
+    return None
+
+def check_probe_dataset(formargs: dict,
+                        species_id,
+                        population_id) -> Optional[tuple[str, Response]]:
+    """Check for the ProbeSet dataset."""
+    dsetinfopg = redirect_on_error("upload.rqtl2.select_dataset_info",
+                                   species_id=species_id,
+                                   population_id=population_id)
+    if not bool(formargs.get("probe-dataset-id")):
+        return "No probeset dataset was selected!", dsetinfopg
+
+    return None
+
+def with_errors(conn: mdb.Connection, endpointthunk: Callable, *checkerstrings):
+    """Run 'endpointthunk' with error checking."""
+    formargs = {**dict(request.args), **dict(request.form)}
+    species_id = formargs.get("species_id") or 0
+    population_id = formargs.get("population_id") or 0
+    fns = {
+        "species":  lambda fargs: check_species(conn, fargs),
+        "population": lambda fargs: check_population(
+            conn, fargs, species_id),
+        "rqtl2_bundle_file": lambda fargs: check_r_qtl2_bundle(
+            fargs, species_id, population_id),
+        "geno_dataset": lambda fargs: check_geno_dataset(
+            fargs, species_id, population_id),
+        "probeset_study": lambda fargs: check_probe_study(
+            fargs, species_id, population_id),
+        "probeset_dataset": lambda fargs: check_probe_dataset(
+            fargs, species_id, population_id)
+    }
+    errors = tuple(item for item in form_errors(
+        formargs, *(fns[chkstr] for chkstr in checkerstrings)) if item is not None)
+    if len(errors) > 0:
+        flash(errors[0][0], "alert-error error-rqtl2")
+        return errors[0][1]
+
+    return endpointthunk()
+
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/select-geno-dataset"),
              methods=["POST"])
 def select_geno_dataset(species_id: int, population_id: int):
     """Select from existing geno datasets."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset")
-        if bool(error):
-            return error
-
-        geno_dset = geno_datasets_by_species_and_population(
-            conn, species_id, population_id)
-        if not bool(geno_dset):
-            flash("No genotype dataset was provided!",
-                  "alert-error error-rqtl2")
-            return redirect(url_for("upload.rqtl2.select_geno_dataset",
+        def __thunk__():
+            geno_dset = geno_datasets_by_species_and_population(
+                conn, species_id, population_id)
+            if not bool(geno_dset):
+                flash("No genotype dataset was provided!",
+                      "alert-error error-rqtl2")
+                return redirect(url_for("upload.rqtl2.select_geno_dataset",
+                                        species_id=species_id,
+                                        population_id=population_id,
+                                        pgsrc="error"),
+                                code=307)
+
+            flash("Genotype accepted", "alert-success error-rqtl2")
+            return redirect(url_for("upload.rqtl2.select_dataset_info",
                                     species_id=species_id,
                                     population_id=population_id,
-                                    pgsrc="error"),
+                                    pgsrc="upload.rqtl2.select_geno_dataset"),
                             code=307)
 
-        flash("Genotype accepted", "alert-success error-rqtl2")
-        return redirect(url_for("upload.rqtl2.select_dataset_info",
-                                species_id=species_id,
-                                population_id=population_id,
-                                pgsrc="upload.rqtl2.select_geno_dataset"),
-                        code=307)
+        return with_errors(
+            conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/create-geno-dataset"),
@@ -344,50 +408,50 @@ def select_geno_dataset(species_id: int, population_id: int):
 def create_geno_dataset(species_id: int, population_id: int):
     """Create a new geno dataset."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(conn, "species", "population", "rqtl2_bundle_file")
-        if bool(error):
-            return error
-
-        sgeno_page = redirect(url_for("upload.rqtl2.select_dataset_info",
-                                      species_id=species_id,
-                                      population_id=population_id,
-                                      pgsrc="error"),
-                              code=307)
-        errorclasses = "alert-error error-rqtl2 error-rqtl2-create-geno-dataset"
-        if not bool(request.form.get("dataset-name")):
-            flash("You must provide the dataset name", errorclasses)
-            return sgeno_page
-        if not bool(request.form.get("dataset-fullname")):
-            flash("You must provide the dataset full name", errorclasses)
-            return sgeno_page
-        public = 2 if request.form.get("dataset-public") == "on" else 0
-
-        with conn.cursor(cursorclass=DictCursor) as cursor:
-            datasetname = request.form["dataset-name"]
-            new_dataset = {
-                "name": datasetname,
-                "fname": request.form.get("dataset-fullname"),
-                "sname": request.form.get("dataset-shortname") or datasetname,
-                "today": date.today().isoformat(),
-                "pub": public,
-                "isetid": population_id
-            }
-            cursor.execute(
-                "INSERT INTO GenoFreeze("
-                "Name, FullName, ShortName, CreateTime, public, InbredSetId"
-                ") "
-                "VALUES("
-                "%(name)s, %(fname)s, %(sname)s, %(today)s, %(pub)s, %(isetid)s"
-                ")",
-                new_dataset)
-            flash("Created dataset successfully.", "alert-success")
-            return render_template(
-                "rqtl2/create-geno-dataset-success.html",
-                species=species_by_id(conn, species_id),
-                population=population_by_species_and_id(
-                    conn, species_id, population_id),
-                rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
-                geno_dataset={**new_dataset, "id": cursor.lastrowid})
+        def __thunk__():
+            sgeno_page = redirect(url_for("upload.rqtl2.select_dataset_info",
+                                          species_id=species_id,
+                                          population_id=population_id,
+                                          pgsrc="error"),
+                                  code=307)
+            errorclasses = "alert-error error-rqtl2 error-rqtl2-create-geno-dataset"
+            if not bool(request.form.get("dataset-name")):
+                flash("You must provide the dataset name", errorclasses)
+                return sgeno_page
+            if not bool(request.form.get("dataset-fullname")):
+                flash("You must provide the dataset full name", errorclasses)
+                return sgeno_page
+            public = 2 if request.form.get("dataset-public") == "on" else 0
+
+            with conn.cursor(cursorclass=DictCursor) as cursor:
+                datasetname = request.form["dataset-name"]
+                new_dataset = {
+                    "name": datasetname,
+                    "fname": request.form.get("dataset-fullname"),
+                    "sname": request.form.get("dataset-shortname") or datasetname,
+                    "today": date.today().isoformat(),
+                    "pub": public,
+                    "isetid": population_id
+                }
+                cursor.execute(
+                    "INSERT INTO GenoFreeze("
+                    "Name, FullName, ShortName, CreateTime, public, InbredSetId"
+                    ") "
+                    "VALUES("
+                    "%(name)s, %(fname)s, %(sname)s, %(today)s, %(pub)s, %(isetid)s"
+                    ")",
+                    new_dataset)
+                flash("Created dataset successfully.", "alert-success")
+                return render_template(
+                    "rqtl2/create-geno-dataset-success.html",
+                    species=species_by_id(conn, species_id),
+                    population=population_by_species_and_id(
+                        conn, species_id, population_id),
+                    rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
+                    geno_dataset={**new_dataset, "id": cursor.lastrowid})
+
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/select-probeset-study"),
@@ -395,21 +459,19 @@ def create_geno_dataset(species_id: int, population_id: int):
 def select_probeset_study(species_id: int, population_id: int):
     """Select or create a probeset study."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset",
-            "probe-study-id")
-        if bool(error):
-            return error
+        def __thunk__():
+            summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
+                                            species_id=species_id,
+                                            population_id=population_id),
+                                    code=307)
+            if not bool(probeset_study_by_id(conn, int(request.form["probe-study-id"]))):
+                flash("Invalid study selected!", "alert-error error-rqtl2")
+                return summary_page
 
-        summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
-                                        species_id=species_id,
-                                        population_id=population_id),
-                                code=307)
-        if not bool(probeset_study_by_id(conn, int(request.form["probe-study-id"]))):
-            flash("Invalid study selected!", "alert-error error-rqtl2")
             return summary_page
-
-        return summary_page
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file", "geno_dataset",
+                           "probeset_study")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/select-probeset-dataset"),
@@ -417,21 +479,20 @@ def select_probeset_study(species_id: int, population_id: int):
 def select_probeset_dataset(species_id: int, population_id: int):
     """Select or create a probeset dataset."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset",
-            "probe-study-id", "probe-dataset-id")
-        if bool(error):
-            return error
+        def __thunk__():
+            summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
+                                            species_id=species_id,
+                                            population_id=population_id),
+                                    code=307)
+            if not bool(probeset_study_by_id(conn, int(request.form["probe-study-id"]))):
+                flash("Invalid study selected!", "alert-error error-rqtl2")
+                return summary_page
 
-        summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
-                                        species_id=species_id,
-                                        population_id=population_id),
-                                code=307)
-        if not bool(probeset_study_by_id(conn, int(request.form["probe-study-id"]))):
-            flash("Invalid study selected!", "alert-error error-rqtl2")
             return summary_page
 
-        return summary_page
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file", "geno_dataset",
+                           "probeset_study", "probeset_dataset")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/create-probeset-study"),
@@ -439,42 +500,41 @@ def select_probeset_dataset(species_id: int, population_id: int):
 def create_probeset_study(species_id: int, population_id: int):
     """Create a new probeset study."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset")
-        if bool(error):
-            return error
-
-        form = request.form
-        select_study_page = redirect(
-            url_for("upload.rqtl2.select_probeset_study",
-                    species_id=species_id,
-                    population_id=population_id),
-            code=307)
-
-        if not (bool(form.get("platformid")) and
-                bool(platform_by_id(conn, int(form["platformid"])))):
-            flash("Invalid platform selected.", "alert-error error-rqtl2")
-            return select_study_page
-
-        if not (bool(form.get("tissueid")) and
-                bool(tissue_by_id(conn, int(form["tissueid"])))):
-            flash("Invalid tissue selected.", "alert-error error-rqtl2")
-            return select_study_page
-
-        study = probeset_create_study(
-            conn, population_id, int(form["platformid"]), int(form["tissueid"]),
-            form["studyname"], form.get("studyfullname") or "",
-            form.get("studyshortname") or "")
-        return render_template(
-            "rqtl2/create-probe-study-success.html",
-            species=species_by_id(conn, species_id),
-            population=population_by_species_and_id(
-                conn, species_id, population_id),
-            rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
-            geno_dataset=geno_dataset_by_id(
-                conn,
-                int(request.form["geno-dataset-id"])),
-            study=study)
+        def __thunk__():
+            form = request.form
+            select_study_page = redirect(
+                url_for("upload.rqtl2.select_probeset_study",
+                        species_id=species_id,
+                        population_id=population_id),
+                code=307)
+
+            if not (bool(form.get("platformid")) and
+                    bool(platform_by_id(conn, int(form["platformid"])))):
+                flash("Invalid platform selected.", "alert-error error-rqtl2")
+                return select_study_page
+
+            if not (bool(form.get("tissueid")) and
+                    bool(tissue_by_id(conn, int(form["tissueid"])))):
+                flash("Invalid tissue selected.", "alert-error error-rqtl2")
+                return select_study_page
+
+            study = probeset_create_study(
+                conn, population_id, int(form["platformid"]), int(form["tissueid"]),
+                form["studyname"], form.get("studyfullname") or "",
+                form.get("studyshortname") or "")
+            return render_template(
+                "rqtl2/create-probe-study-success.html",
+                species=species_by_id(conn, species_id),
+                population=population_by_species_and_id(
+                    conn, species_id, population_id),
+                rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
+                geno_dataset=geno_dataset_by_id(
+                    conn,
+                    int(request.form["geno-dataset-id"])),
+                study=study)
+
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file", "geno_dataset")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/create-probeset-dataset"),
@@ -482,57 +542,56 @@ def create_probeset_study(species_id: int, population_id: int):
 def create_probeset_dataset(species_id: int, population_id: int):#pylint: disable=[too-many-return-statements]
     """Create a new probeset dataset."""
     with database_connection(app.config["SQL_URI"]) as conn:
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset",
-            "probe-study-id")
-        if bool(error):
-            return error
-
-        form = request.form
-        summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
-                                        species_id=species_id,
-                                        population_id=population_id),
-                                code=307)
-        if not bool(form.get("averageid")):
-            flash("Averaging method not selected!", "alert-error error-rqtl2")
-            return summary_page
-        if not bool(form.get("datasetname")):
-            flash("Dataset name not provided!", "alert-error error-rqtl2")
-            return summary_page
-        if not bool(form.get("datasetfullname")):
-            flash("Dataset full name not provided!", "alert-error error-rqtl2")
-            return summary_page
-
-        study = probeset_study_by_id(conn, int(form["probe-study-id"]))
-        if not bool(study):
-            flash("Invalid ProbeSet study provided!", "alert-error error-rqtl2")
-            return summary_page
-
-        avgmethod = averaging_method_by_id(conn, int(form["averageid"]))
-        if not bool(avgmethod):
-            flash("Invalid averaging method provided!", "alert-error error-rqtl2")
-            return summary_page
+        def __thunk__():
+            form = request.form
+            summary_page = redirect(url_for("upload.rqtl2.select_dataset_info",
+                                            species_id=species_id,
+                                            population_id=population_id),
+                                    code=307)
+            if not bool(form.get("averageid")):
+                flash("Averaging method not selected!", "alert-error error-rqtl2")
+                return summary_page
+            if not bool(form.get("datasetname")):
+                flash("Dataset name not provided!", "alert-error error-rqtl2")
+                return summary_page
+            if not bool(form.get("datasetfullname")):
+                flash("Dataset full name not provided!", "alert-error error-rqtl2")
+                return summary_page
+
+            study = probeset_study_by_id(conn, int(form["probe-study-id"]))
+            if not bool(study):
+                flash("Invalid ProbeSet study provided!", "alert-error error-rqtl2")
+                return summary_page
+
+            avgmethod = averaging_method_by_id(conn, int(form["averageid"]))
+            if not bool(avgmethod):
+                flash("Invalid averaging method provided!", "alert-error error-rqtl2")
+                return summary_page
+
+            dset = probeset_create_dataset(conn,
+                                           int(form["probe-study-id"]),
+                                           int(form["averageid"]),
+                                           form["datasetname"],
+                                           form["datasetfullname"],
+                                           form["datasetshortname"],
+                                           form["datasetpublic"] == "on",
+                                           form.get("datasetdatascale", "log2"))
+            return render_template(
+                "rqtl2/create-probe-dataset-success.html",
+                species=species_by_id(conn, species_id),
+                population=population_by_species_and_id(
+                    conn, species_id, population_id),
+                rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
+                geno_dataset=geno_dataset_by_id(
+                    conn,
+                    int(request.form["geno-dataset-id"])),
+                study=study,
+                avgmethod=avgmethod,
+                dataset=dset)
 
-        dset = probeset_create_dataset(conn,
-                                       int(form["probe-study-id"]),
-                                       int(form["averageid"]),
-                                       form["datasetname"],
-                                       form["datasetfullname"],
-                                       form["datasetshortname"],
-                                       form["datasetpublic"] == "on",
-                                       form.get("datasetdatascale", "log2"))
-        return render_template(
-            "rqtl2/create-probe-dataset-success.html",
-            species=species_by_id(conn, species_id),
-            population=population_by_species_and_id(
-                conn, species_id, population_id),
-            rqtl2_bundle_file=request.form["rqtl2_bundle_file"],
-            geno_dataset=geno_dataset_by_id(
-                conn,
-                int(request.form["geno-dataset-id"])),
-            study=study,
-            avgmethod=avgmethod,
-            dataset=dset)
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file", "geno_dataset",
+                           "probeset_study")
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/dataset-info"),
@@ -544,63 +603,63 @@ def select_dataset_info(species_id: int, population_id: int):
     """
     form = request.form
     with database_connection(app.config["SQL_URI"]) as conn:
-        error_page = check_errors(conn, "species", "population", "rqtl2_bundle_file")
-        if bool(error_page):
-            return error_page
-
-        species = species_by_id(conn, species_id)
-        population = population_by_species_and_id(
-            conn, species_id, population_id)
-        thefile = fullpath(form["rqtl2_bundle_file"])
-        with ZipFile(str(thefile), "r") as zfile:
-            cdata = r_qtl2.control_data(zfile)
-            if "geno" in cdata and not bool(form.get("geno-dataset-id")):
-                return render_template(
-                    "rqtl2/select-geno-dataset.html",
-                    species=species,
-                    population=population,
-                    rqtl2_bundle_file=thefile.name,
-                    datasets=geno_datasets_by_species_and_population(
-                        conn, species_id, population_id))
-            geno_dataset = geno_dataset_by_id(conn, int(form["geno-dataset-id"]))
+        def __thunk__():
+            species = species_by_id(conn, species_id)
+            population = population_by_species_and_id(
+                conn, species_id, population_id)
+            thefile = fullpath(form["rqtl2_bundle_file"])
+            with ZipFile(str(thefile), "r") as zfile:
+                cdata = r_qtl2.control_data(zfile)
+                if "geno" in cdata and not bool(form.get("geno-dataset-id")):
+                    return render_template(
+                        "rqtl2/select-geno-dataset.html",
+                        species=species,
+                        population=population,
+                        rqtl2_bundle_file=thefile.name,
+                        datasets=geno_datasets_by_species_and_population(
+                            conn, species_id, population_id))
+                geno_dataset = geno_dataset_by_id(conn, int(form["geno-dataset-id"]))
+
+                if "pheno" in cdata and not bool(form.get("probe-study-id")):
+                    return render_template(
+                        "rqtl2/select-probeset-study-id.html",
+                        species=species,
+                        population=population,
+                        rqtl2_bundle_file=thefile.name,
+                        geno_dataset=geno_dataset,
+                        studies=probeset_studies_by_species_and_population(
+                                conn, species_id, population_id),
+                        platforms=platforms_by_species(conn, species_id),
+                        tissues=all_tissues(conn))
+                probeset_study = probeset_study_by_id(
+                    conn, int(form["probe-study-id"]))
+
+                if "pheno" in cdata and not bool(form.get("probe-dataset-id")):
+                    return render_template(
+                        "rqtl2/select-probeset-dataset.html",
+                        species=species,
+                        population=population,
+                        rqtl2_bundle_file=thefile.name,
+                        geno_dataset=geno_dataset,
+                        probe_study=probeset_study,
+                        datasets=probeset_datasets_by_study(
+                            conn, int(form["probe-study-id"])),
+                        avgmethods=averaging_methods(conn))
+                probeset_study = probeset_study_by_id(
+                    conn, int(form["probe-study-id"]))
+                probeset_dataset = probeset_dataset_by_id(
+                    conn, int(form["probe-dataset-id"]))
+
+            return render_template("rqtl2/summary-info.html",
+                                   species=species,
+                                   population=population,
+                                   rqtl2_bundle_file=thefile.name,
+                                   geno_dataset=geno_dataset,
+                                   probe_study=probeset_study,
+                                   probe_dataset=probeset_dataset)
 
-            if "pheno" in cdata and not bool(form.get("probe-study-id")):
-                return render_template(
-                    "rqtl2/select-probeset-study-id.html",
-                    species=species,
-                    population=population,
-                    rqtl2_bundle_file=thefile.name,
-                    geno_dataset=geno_dataset,
-                    studies=probeset_studies_by_species_and_population(
-                            conn, species_id, population_id),
-                    platforms=platforms_by_species(conn, species_id),
-                    tissues=all_tissues(conn))
-            probeset_study = probeset_study_by_id(
-                conn, int(form["probe-study-id"]))
-
-            if "pheno" in cdata and not bool(form.get("probe-dataset-id")):
-                return render_template(
-                    "rqtl2/select-probeset-dataset.html",
-                    species=species,
-                    population=population,
-                    rqtl2_bundle_file=thefile.name,
-                    geno_dataset=geno_dataset,
-                    probe_study=probeset_study,
-                    datasets=probeset_datasets_by_study(
-                        conn, int(form["probe-study-id"])),
-                    avgmethods=averaging_methods(conn))
-            probeset_study = probeset_study_by_id(
-                conn, int(form["probe-study-id"]))
-            probeset_dataset = probeset_dataset_by_id(
-                conn, int(form["probe-dataset-id"]))
-
-    return render_template("rqtl2/summary-info.html",
-                           species=species,
-                           population=population,
-                           rqtl2_bundle_file=thefile.name,
-                           geno_dataset=geno_dataset,
-                           probe_study=probeset_study,
-                           probe_dataset=probeset_dataset)
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file")
 
 
 
@@ -612,50 +671,49 @@ def confirm_bundle_details(species_id: int, population_id: int):
     redisuri = app.config["REDIS_URL"]
     with (database_connection(app.config["SQL_URI"]) as conn,
           Redis.from_url(redisuri, decode_responses=True) as rconn):
-        error = check_errors(
-            conn, "species", "population", "rqtl2_bundle_file", "geno-dataset",
-            "probe-study-id", "probe-dataset-id")
-        if bool(error):
-            return error
-
-        redis_ttl_seconds = app.config["JOBS_TTL_SECONDS"]
-        jobid = str(uuid4())
-        _job = jobs.launch_job(
-            jobs.initialise_job(
-                rconn,
-                jobs.jobsnamespace(),
-                jobid,
-                [
-                    sys.executable, "-m", "scripts.process_rqtl2_bundle",
-                    app.config["SQL_URI"], app.config["REDIS_URL"],
-                    jobs.jobsnamespace(), jobid, "--redisexpiry",
-                    str(redis_ttl_seconds)],
-                "R/qtl2 Bundle Upload",
-                redis_ttl_seconds,
-                {
-                    "bundle-metadata": json.dumps({
-                        "speciesid": species_id,
-                        "populationid": population_id,
-                        "rqtl2-bundle-file": str(fullpath(
-                            request.form["rqtl2_bundle_file"])),
-                        "geno-dataset-id": request.form.get(
-                            "geno-dataset-id", ""),
-                        "probe-study-id": request.form.get(
-                            "probe-study-id", ""),
-                        "probe-dataset-id": request.form.get(
-                            "probe-dataset-id", ""),
-                        **({
-                            "platformid": probeset_study_by_id(
-                                conn,
-                                int(request.form["probe-study-id"]))["ChipId"]
-                        } if bool(request.form.get("probe-study-id")) else {})
-                    })
-                }),
-            redisuri,
-            f"{app.config['UPLOAD_FOLDER']}/job_errors")
-
-        return redirect(url_for("upload.rqtl2.rqtl2_processing_status",
-                                jobid=jobid))
+        def __thunk__():
+            redis_ttl_seconds = app.config["JOBS_TTL_SECONDS"]
+            jobid = str(uuid4())
+            _job = jobs.launch_job(
+                jobs.initialise_job(
+                    rconn,
+                    jobs.jobsnamespace(),
+                    jobid,
+                    [
+                        sys.executable, "-m", "scripts.process_rqtl2_bundle",
+                        app.config["SQL_URI"], app.config["REDIS_URL"],
+                        jobs.jobsnamespace(), jobid, "--redisexpiry",
+                        str(redis_ttl_seconds)],
+                    "R/qtl2 Bundle Upload",
+                    redis_ttl_seconds,
+                    {
+                        "bundle-metadata": json.dumps({
+                            "speciesid": species_id,
+                            "populationid": population_id,
+                            "rqtl2-bundle-file": str(fullpath(
+                                request.form["rqtl2_bundle_file"])),
+                            "geno-dataset-id": request.form.get(
+                                "geno-dataset-id", ""),
+                            "probe-study-id": request.form.get(
+                                "probe-study-id", ""),
+                            "probe-dataset-id": request.form.get(
+                                "probe-dataset-id", ""),
+                            **({
+                                "platformid": probeset_study_by_id(
+                                    conn,
+                                    int(request.form["probe-study-id"]))["ChipId"]
+                            } if bool(request.form.get("probe-study-id")) else {})
+                        })
+                    }),
+                redisuri,
+                f"{app.config['UPLOAD_FOLDER']}/job_errors")
+
+            return redirect(url_for("upload.rqtl2.rqtl2_processing_status",
+                                    jobid=jobid))
+
+        return with_errors(conn, __thunk__, "species", "population",
+                           "rqtl2_bundle_file", "geno_dataset",
+                           "probeset_study", "probeset_dataset")
 
 @rqtl2.route("/status/<uuid:jobid>")
 def rqtl2_processing_status(jobid: UUID):