about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-04-08 12:49:33 +0300
committerFrederick Muriuki Muriithi2024-04-08 12:58:06 +0300
commit61362063e93dbacc75d093c6862ddc0aef534198 (patch)
tree0bc4215bbd04d31769aee83c7f24eff18185bd44
parent619bf2e80dd6c225a4121507f0b9374598b94ea6 (diff)
downloadgn-uploader-61362063e93dbacc75d093c6862ddc0aef534198.tar.gz
Refactor: Make error-checking more robust
Rather than maintaining a dictionary of possible error-checking
functions, this commit allows passing in the error-checking functions
necessary for each point. This makes the code easier to extend by
simply defining a new error-checking function and passing it in to the
error-checking "driver".
-rw-r--r--qc_app/upload/rqtl2.py235
1 files changed, 184 insertions, 51 deletions
diff --git a/qc_app/upload/rqtl2.py b/qc_app/upload/rqtl2.py
index 04bfa5b..8b5ac01 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."""
+"""Module to handle uploading of R/qtl2 bundles."""#pylint: disable=[too-many-lines]
 import sys
 import json
 import traceback
 from pathlib import Path
 from datetime import date
 from uuid import UUID, uuid4
+from functools import partial
 from zipfile import ZipFile, is_zipfile
-from typing import Union, Callable, Optional, Iterable
+from typing import Union, Callable, Optional
 
 import MySQLdb as mdb
 from redis import Redis
@@ -262,11 +263,6 @@ def rqtl2_bundle_qc_status(jobid: UUID):
             return render_template("rqtl2/no-such-job.html", jobid=jobid)
 
 
-def form_errors(formargs, *errorcheckers) -> Iterable[tuple[str, Response]]:
-    """Retrieve all errors from the form inputs"""
-    return (checker(formargs) for checker in errorcheckers)
-
-
 def redirect_on_error(flaskroute, **kwargs):
     """Utility to redirect on error"""
     return redirect(url_for(flaskroute, **kwargs, pgsrc="error"),
@@ -327,21 +323,61 @@ def check_r_qtl2_bundle(formargs: dict,
     return None
 
 
-def check_geno_dataset(formargs: dict,
+def check_geno_dataset(conn: mdb.Connection,
+                       formargs: dict,
                        species_id,
                        population_id) -> Optional[tuple[str, Response]]:
     """Check for the Genotype dataset."""
-    genodsetpg = redirect_on_error("upload.rqtl2.select_geno_dataset",
+    genodsetpg = redirect_on_error("upload.rqtl2.select_dataset_info",
                                    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)
 
+    with conn.cursor(cursorclass=DictCursor) as cursor:
+        cursor.execute("SELECT * FROM GenoFreeze WHERE Id=%s",
+                       (formargs["geno-dataset-id"],))
+        results = cursor.fetchall()
+        if not bool(results):
+            return ("No genotype dataset with the provided identifier exists.",
+                    genodsetpg)
+        if len(results) > 1:
+            return (
+                "Data corruption: More than one genotype dataset with the same "
+                "identifier.",
+                genodsetpg)
+
+    return None
+
+def check_tissue(
+        conn: mdb.Connection,formargs: dict) -> Optional[tuple[str, Response]]:
+    """Check for tissue/organ/biological material."""
+    selectdsetpg = redirect_on_error("upload.rqtl2.select_dataset_info",
+                                     species_id=formargs["species_id"],
+                                     population_id=formargs["population_id"])
+    if not bool(formargs.get("tissueid", "").strip()):
+        return ("No tissue/organ/biological material provided.", selectdsetpg)
+
+    with conn.cursor(cursorclass=DictCursor) as cursor:
+        cursor.execute("SELECT * FROM Tissue WHERE Id=%s",
+                       (formargs["tissueid"],))
+        results = cursor.fetchall()
+        if not bool(results):
+            return ("No tissue/organ with the provided identifier exists.",
+                    selectdsetpg)
+
+        if len(results) > 1:
+            return (
+                "Data corruption: More than one tissue/organ with the same "
+                "identifier.",
+                selectdsetpg)
+
     return None
 
 
-def check_probe_study(formargs: dict,
+def check_probe_study(conn: mdb.Connection,
+                      formargs: dict,
                       species_id,
                       population_id) -> Optional[tuple[str, Response]]:
     """Check for the ProbeSet study."""
@@ -351,10 +387,15 @@ def check_probe_study(formargs: dict,
     if not bool(formargs.get("probe-study-id")):
         return "No probeset study was selected!", dsetinfopg
 
+    if not bool(probeset_study_by_id(conn, formargs["probe-study-id"])):
+        return ("No probeset study with the provided identifier exists",
+                dsetinfopg)
+
     return None
 
 
-def check_probe_dataset(formargs: dict,
+def check_probe_dataset(conn: mdb.Connection,
+                        formargs: dict,
                         species_id,
                         population_id) -> Optional[tuple[str, Response]]:
     """Check for the ProbeSet dataset."""
@@ -364,29 +405,18 @@ def check_probe_dataset(formargs: dict,
     if not bool(formargs.get("probe-dataset-id")):
         return "No probeset dataset was selected!", dsetinfopg
 
+    if not bool(probeset_dataset_by_id(conn, formargs["probe-dataset-id"])):
+        return ("No probeset dataset with the provided identifier exists",
+                dsetinfopg)
+
     return None
 
 
-def with_errors(conn: mdb.Connection, endpointthunk: Callable, *checkerstrings):
+def with_errors(endpointthunk: Callable, *checkfns):
     """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)
+    errors = tuple(item for item in (_fn(formargs=formargs) for _fn in checkfns)
+                   if item is not None)
     if len(errors) > 0:
         flash(errors[0][0], "alert-error error-rqtl2")
         return errors[0][1]
@@ -419,8 +449,17 @@ def select_geno_dataset(species_id: int, population_id: int):
                                     pgsrc="upload.rqtl2.select_geno_dataset"),
                             code=307)
 
-        return with_errors(
-            conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population, conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -484,8 +523,12 @@ def create_geno_dataset(species_id: int, population_id: int):
                     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")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population, conn=conn),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -505,8 +548,18 @@ def select_tissue(species_id: int, population_id: int):
                                     pgsrc="upload.rqtl2.select_geno_dataset"),
                             code=307)
 
-        return with_errors(
-            conn, __thunk__, "species", "population", "rqtl2_bundle_file", "geno_dataset")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
               "/rqtl2-bundle/create-tissue"),
@@ -568,9 +621,23 @@ def select_probeset_study(species_id: int, population_id: int):
                 return summary_page
 
             return summary_page
-        return with_errors(conn, __thunk__, "species", "population",
-                           "rqtl2_bundle_file", "geno_dataset",
-                           "probeset_study")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_tissue, conn=conn),
+                           partial(check_probe_study,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -590,9 +657,27 @@ def select_probeset_dataset(species_id: int, population_id: int):
 
             return summary_page
 
-        return with_errors(conn, __thunk__, "species", "population",
-                           "rqtl2_bundle_file", "geno_dataset",
-                           "probeset_study", "probeset_dataset")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_tissue, conn=conn),
+                           partial(check_probe_study,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_probe_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -642,8 +727,19 @@ def create_probeset_study(species_id: int, population_id: int):
                     int(request.form["geno-dataset-id"])),
                 study=study)
 
-        return with_errors(conn, __thunk__, "species", "population",
-                           "rqtl2_bundle_file", "geno_dataset")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_tissue, conn=conn))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -713,9 +809,23 @@ def create_probeset_dataset(species_id: int, population_id: int):#pylint: disabl
                 avgmethod=avgmethod,
                 dataset=dset)
 
-        return with_errors(conn, __thunk__, "species", "population",
-                           "rqtl2_bundle_file", "geno_dataset",
-                           "probeset_study")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_tissue, conn=conn),
+                           partial(check_probe_study,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -796,8 +906,14 @@ def select_dataset_info(species_id: int, population_id: int):
                                    probe_study=probeset_study,
                                    probe_dataset=probeset_dataset)
 
-        return with_errors(conn, __thunk__, "species", "population",
-                           "rqtl2_bundle_file")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route(("/upload/species/<int:species_id>/population/<int:population_id>"
@@ -848,9 +964,26 @@ def confirm_bundle_details(species_id: int, population_id: int):
             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")
+        return with_errors(__thunk__,
+                           partial(check_species, conn=conn),
+                           partial(check_population,
+                                   conn=conn,
+                                   species_id=species_id),
+                           partial(check_r_qtl2_bundle,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_geno_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_probe_study,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id),
+                           partial(check_probe_dataset,
+                                   conn=conn,
+                                   species_id=species_id,
+                                   population_id=population_id))
 
 
 @rqtl2.route("/status/<uuid:jobid>")