about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn3/api/case_attributes.py69
-rw-r--r--gn3/db/case_attributes.py65
-rw-r--r--gn3/db/sample_data.py4
-rw-r--r--tests/unit/db/test_case_attributes.py2
4 files changed, 59 insertions, 81 deletions
diff --git a/gn3/api/case_attributes.py b/gn3/api/case_attributes.py
index 536d74d..5662bfe 100644
--- a/gn3/api/case_attributes.py
+++ b/gn3/api/case_attributes.py
@@ -1,5 +1,6 @@
 """Implement case-attribute manipulations."""
 from typing import Union
+from pathlib import Path
 
 from functools import reduce
 from urllib.parse import urljoin
@@ -181,21 +182,22 @@ def inbredset_case_attribute_values(inbredset_id: int) -> Response:
         return jsonify(__case_attribute_values_by_inbred_set__(conn, inbredset_id))
 
 
+# pylint: disable=[too-many-locals]
 @caseattr.route("/<int:inbredset_id>/edit", methods=["POST"])
 @require_token
-def edit_case_attributes(inbredset_id: int, auth_token=None) -> Response:
+def edit_case_attributes(inbredset_id: int, auth_token=None) -> tuple[Response, int]:
     """Edit the case attributes for `InbredSetId` based on data received.
 
     :inbredset_id: Identifier for the population that the case attribute belongs
     :auth_token: A validated JWT from the auth server
     """
     with database_connection(current_app.config["SQL_URI"]) as conn, conn.cursor() as cursor:
-        data = request.json["edit-data"]
+        data = request.json["edit-data"]  # type: ignore
         modified = {
             "inbredset_id": inbredset_id,
             "Modifications": {},
         }
-        original, current = {}, {}
+        original, current = {}, {}  # type: ignore
 
         for key, value in data.items():
             strain, case_attribute = key.split(":")
@@ -205,16 +207,18 @@ def edit_case_attributes(inbredset_id: int, auth_token=None) -> Response:
             if not original.get(strain):
                 original[strain] = {}
             original[strain][case_attribute] = value["Original"]
-        modified["Modifications"]["Original"] = original
-        modified["Modifications"]["Current"] = current
+        modified["Modifications"]["Original"] = original  # type: ignore
+        modified["Modifications"]["Current"] = current  # type: ignore
         edit = CaseAttributeEdit(
             inbredset_id=inbredset_id,
             status=EditStatus.review,
             user_id=auth_token["jwt"]["sub"],
             changes=modified
         )
+        directory = (Path(current_app.config["LMDB_DATA_PATH"]) /
+                     "case-attributes" / str(inbredset_id))
         _id = queue_edit(cursor=cursor,
-                         directory=current_app.config["LMDB_DATA_PATH"],
+                         directory=directory,
                          edit=edit)
         try:
             required_access(auth_token,
@@ -223,31 +227,31 @@ def edit_case_attributes(inbredset_id: int, auth_token=None) -> Response:
                              "system:inbredset:apply-case-attribute-edit"))
             match apply_change(
                     cursor, change_type=EditStatus.approved,
-                    change_id=_id,
-                    directory=current_app.config["LMDB_DATA_PATH"]
+                    change_id=_id,  # type: ignore
+                    directory=directory
             ):
                 case True:
                     return jsonify({
                         "diff-status": "applied",
                         "message": ("The changes to the case-attributes have been "
                                     "applied successfully.")
-                    })
+                    }), 201
                 case _:
                     return jsonify({
                         "diff-status": "no changes to be applied",
                         "message": ("There were no changes to be made ")
-                    })
+                    }), 200
         except AuthorisationError as _auth_err:
             return jsonify({
                 "diff-status": "queued",
                 "message": ("The changes to the case-attributes have been "
                             "queued for approval."),
-            }), 401
+            }), 201
 
 
 @caseattr.route("/<int:inbredset_id>/diff/list", methods=["GET"])
 @require_token
-def list_diffs(inbredset_id: int, auth_token=None) -> Response:
+def list_diffs(inbredset_id: int, auth_token=None) -> tuple[Response, int]:
     """List any changes that have not been approved/rejected."""
     try:
         required_access(auth_token,
@@ -256,9 +260,9 @@ def list_diffs(inbredset_id: int, auth_token=None) -> Response:
                          "system:inbredset:apply-case-attribute-edit"))
         with (database_connection(current_app.config["SQL_URI"]) as conn,
               conn.cursor(cursorclass=DictCursor) as cursor):
-            changes = get_changes(cursor, inbredset_id=inbredset_id,
-                                  directory=current_app.config["LMDB_DATA_PATH"])
-            current_app.logger.error(changes)
+            directory = (Path(current_app.config["LMDB_DATA_PATH"]) /
+                         "case-attributes" / str(inbredset_id))
+            changes = get_changes(cursor, directory=directory)
             return jsonify(
                 changes
             ), 200
@@ -270,27 +274,32 @@ def list_diffs(inbredset_id: int, auth_token=None) -> Response:
 
 @caseattr.route("/<int:inbredset_id>/approve/<int:change_id>", methods=["POST"])
 @require_token
-def approve_case_attributes_diff(inbredset_id: int, change_id: int, auth_token=None) -> Response:
+def approve_case_attributes_diff(
+        inbredset_id: int,
+        change_id: int, auth_token=None
+) -> tuple[Response, int]:
     """Approve the changes to the case attributes in the diff."""
     try:
         required_access(auth_token,
                         inbredset_id,
-                        ("system:inbredset:edit-case-attribute"))
-        with database_connection(current_app.config["SQL_URI"]) as conn, \
-                conn.cursor() as cursor:
+                        ("system:inbredset:edit-case-attribute",))
+        with (database_connection(current_app.config["SQL_URI"]) as conn,
+              conn.cursor() as cursor):
+            directory = (Path(current_app.config["LMDB_DATA_PATH"]) /
+                         "case-attributes" / str(inbredset_id))
             match apply_change(cursor, change_type=EditStatus.rejected,
                                change_id=change_id,
-                               directory=current_app.config["LMDB_DATA_PATH"]):
+                               directory=directory):
                 case True:
                     return jsonify({
                         "diff-status": "rejected",
                         "message": (f"Successfully approved # {change_id}")
-                    })
+                    }), 201
                 case _:
                     return jsonify({
                         "diff-status": "queued",
                         "message": (f"Was not able to successfully approve # {change_id}")
-                    })
+                    }), 200
     except AuthorisationError as __auth_err:
         return jsonify({
             "diff-status": "queued",
@@ -300,7 +309,9 @@ def approve_case_attributes_diff(inbredset_id: int, change_id: int, auth_token=N
 
 @caseattr.route("/<int:inbredset_id>/reject/<int:change_id>", methods=["POST"])
 @require_token
-def reject_case_attributes_diff(inbredset_id: int, change_id: int, auth_token=None) -> Response:
+def reject_case_attributes_diff(
+        inbredset_id: int, change_id: int, auth_token=None
+) -> tuple[Response, int]:
     """Reject the changes to the case attributes in the diff."""
     try:
         required_access(auth_token,
@@ -309,20 +320,22 @@ def reject_case_attributes_diff(inbredset_id: int, change_id: int, auth_token=No
                          "system:inbredset:apply-case-attribute-edit"))
         with database_connection(current_app.config["SQL_URI"]) as conn, \
                 conn.cursor() as cursor:
+            directory = (Path(current_app.config["LMDB_DATA_PATH"]) /
+                         "case-attributes" / str(inbredset_id))
             match apply_change(cursor, change_type=EditStatus.rejected,
                                change_id=change_id,
-                               directory=current_app.config["LMDB_DATA_PATH"]):
+                               directory=directory):
                 case True:
                     return jsonify({
                         "diff-status": "rejected",
                         "message": ("The changes to the case-attributes have been "
                                     "rejected.")
-                    })
+                    }), 201
                 case _:
                     return jsonify({
                         "diff-status": "queued",
                         "message": ("Failed to reject changes")
-                    })
+                    }), 200
     except AuthorisationError as __auth_err:
         return jsonify({
             "message": ("You don't have the right privileges to edit this resource.")
@@ -331,7 +344,7 @@ def reject_case_attributes_diff(inbredset_id: int, change_id: int, auth_token=No
 
 @caseattr.route("/<int:inbredset_id>/diff/<int:change_id>/view", methods=["GET"])
 @require_token
-def view_diff(inbredset_id: int, change_id: int, auth_token=None) -> Response:
+def view_diff(inbredset_id: int, change_id: int, auth_token=None) -> tuple[Response, int]:
     """View a diff."""
     try:
         required_access(
@@ -340,7 +353,7 @@ def view_diff(inbredset_id: int, change_id: int, auth_token=None) -> Response:
               conn.cursor(cursorclass=DictCursor) as cursor):
             return jsonify(
                 view_change(cursor, change_id)
-            )
+            ), 200
     except AuthorisationError as __auth_err:
         return jsonify({
             "message": ("You don't have the right privileges to view the diffs.")
diff --git a/gn3/db/case_attributes.py b/gn3/db/case_attributes.py
index b16bbdd..48afc0c 100644
--- a/gn3/db/case_attributes.py
+++ b/gn3/db/case_attributes.py
@@ -4,7 +4,6 @@ from typing import Optional
 from dataclasses import dataclass
 from enum import Enum, auto
 
-import os
 import json
 import pickle
 import lmdb
@@ -52,13 +51,6 @@ def queue_edit(cursor, directory: Path, edit: CaseAttributeEdit) -> Optional[int
 
     Returns:
         int: An id the particular case-attribute that was updated.
-
-    Notes:
-        - Inserts the edit into the `caseattributes_audit` table with status set to
-          `EditStatus.review`.
-        - Uses LMDB to store review IDs under the key b"review" for the given
-          inbredset_id.
-        - The LMDB map_size is set to 8 MB.
     """
     cursor.execute(
         "INSERT INTO "
@@ -67,10 +59,8 @@ def queue_edit(cursor, directory: Path, edit: CaseAttributeEdit) -> Optional[int
         "ON DUPLICATE KEY UPDATE status=%s",
         (str(edit.status), edit.user_id,
          json.dumps(edit.changes), str(EditStatus.review),))
-    directory = f"{directory}/case-attributes/{edit.inbredset_id}"
-    if not os.path.exists(directory):
-        os.makedirs(directory)
-    env = lmdb.open(directory, map_size=8_000_000)  # 1 MB
+    directory.mkdir(parents=True, exist_ok=True)
+    env = lmdb.open(directory.as_posix(), map_size=8_000_000)  # 1 MB
     with env.begin(write=True) as txn:
         review_ids = set()
         if reviews := txn.get(b"review"):
@@ -143,16 +133,8 @@ def view_change(cursor, change_id: int) -> dict:
 
     Returns:
         dict: The deserialized JSON diff data as a dictionary if the
-              record exists and contains valid JSON; otherwise, an empty dictionary.
-
-    Notes:
-        - The function assumes `change_id` is a valid integer
-          corresponding to a record in `caseattributes_audit`.
-        - The `json_diff_data` column is expected to contain a valid
-          JSON string or None.
-        - Uses parameterized queries to prevent SQL injection.
-        - The second column returned by `fetchone()` is
-          ignored (denoted by `_`).
+              record exists and contains valid JSON; otherwise, an
+              empty dictionary.
 
     Raises:
         json.JSONDecodeError: If the `json_diff_data` cannot be
@@ -175,21 +157,18 @@ def view_change(cursor, change_id: int) -> dict:
     return {}
 
 
-def get_changes(cursor, inbredset_id: int, directory: Path) -> dict:
-    """Retrieves case attribute changes for a given inbred set,
-    categorized by review status.
+def get_changes(cursor, directory: Path) -> dict:
+    """Retrieves case attribute changes for given lmdb data in
+    directory categorized by review status.
 
-    Fetches change IDs from an LMDB database for the specified
-    `inbredset_id`, categorized as review, approved, or
-    rejected. Queries the `caseattributes_audit` table to retrieve
-    details for these changes using the internal
+    Fetches change IDs from an LMDB database, categorized as review,
+    approved, or rejected. Queries the `caseattributes_audit` table to
+    retrieve details for these changes using the internal
     `__fetch_case_attrs_changes__` function.  Returns a dictionary
     with change details grouped by status.
 
     Args:
         - cursor: A MySQLdb cursor for executing SQL queries.
-        - inbredset_id (int): The ID of the inbred set to filter
-          changes.
         - directory (Path): The base directory path for the LMDB
           database.
 
@@ -201,20 +180,6 @@ def get_changes(cursor, inbredset_id: int, directory: Path) -> dict:
             'time_stamp'). Empty dictionaries are returned for
             categories with no changes.
 
-    Notes:
-        - Creates an LMDB environment in a subdirectory
-          `case-attributes/{inbredset_id}` if it doesn't exist, with a
-          map size of 8 MB.
-        - Uses LMDB to store sets of change IDs under keys 'review',
-          'approved', and 'rejected'.
-        - The `__fetch_case_attrs_changes__` function is called for
-          each non-empty set of change IDs to retrieve details from
-          the `caseattributes_audit` table.
-        - If no change IDs exist for a category, an empty dictionary
-          is returned for that category.
-        - Assumes `inbredset_id` is a valid integer and the LMDB
-          database is properly formatted.
-
     Raises:
         json.JSONDecodeError: If any `json_diff_data` in the audit
             table cannot be deserialized by
@@ -223,11 +188,10 @@ def get_changes(cursor, inbredset_id: int, directory: Path) -> dict:
             cannot be deserialized.
 
     """
-    directory = f"{directory}/case-attributes/{inbredset_id}"
-    if not os.path.exists(directory):
-        os.makedirs(directory)
+    directory.mkdir(parents=True, exist_ok=True)
     review_ids, approved_ids, rejected_ids = set(), set(), set()
-    env = lmdb.open(directory, map_size=8_000_000)  # 1 MB
+    directory.mkdir(parents=True, exist_ok=True)
+    env = lmdb.open(directory.as_posix(), map_size=8_000_000)  # 1 MB
     with env.begin(write=False) as txn:
         if reviews := txn.get(b"review"):
             review_ids = pickle.loads(reviews)
@@ -313,7 +277,8 @@ def apply_change(cursor, change_type: EditStatus, change_id: int, directory: Pat
 
     """
     review_ids, approved_ids, rejected_ids = set(), set(), set()
-    env = lmdb.open(directory, map_size=8_000_000)  # 1 MB
+    directory.mkdir(parents=True, exist_ok=True)
+    env = lmdb.open(directory.as_posix(), map_size=8_000_000)  # 1 MB
     with env.begin(write=True) as txn:
         if reviews := txn.get(b"review"):
             review_ids = pickle.loads(reviews)
diff --git a/gn3/db/sample_data.py b/gn3/db/sample_data.py
index 446d18d..4e01a3a 100644
--- a/gn3/db/sample_data.py
+++ b/gn3/db/sample_data.py
@@ -59,7 +59,7 @@ def __extract_actions(
     return result
 
 def get_mrna_sample_data(
-    conn: Any, probeset_id: int, dataset_name: str, probeset_name: str = None
+    conn: Any, probeset_id: int, dataset_name: str, probeset_name: str = None  # type: ignore
 ) -> Dict:
     """Fetch a mRNA Assay (ProbeSet in the DB) trait's sample data and return it as a dict"""
     with conn.cursor() as cursor:
@@ -130,7 +130,7 @@ WHERE ps.Id = %s AND psf.Name= %s""", (probeset_id, dataset_name))
         return "\n".join(trait_csv)
 
 def get_pheno_sample_data(
-    conn: Any, trait_name: int, phenotype_id: int, group_id: int = None
+    conn: Any, trait_name: int, phenotype_id: int, group_id: int = None  # type: ignore
 ) -> Dict:
     """Fetch a phenotype (Publish in the DB) trait's sample data and return it as a dict"""
     with conn.cursor() as cursor:
diff --git a/tests/unit/db/test_case_attributes.py b/tests/unit/db/test_case_attributes.py
index d9da253..9b0378f 100644
--- a/tests/unit/db/test_case_attributes.py
+++ b/tests/unit/db/test_case_attributes.py
@@ -22,7 +22,7 @@ def test_queue_edit(mocker: MockFixture) -> None:
     mock_conn = mocker.MagicMock()
     with mock_conn.cursor() as cursor:
         type(cursor).lastrowid = 28
-        tmpdir = os.environ.get("TMPDIR", tempfile.gettempdir())
+        tmpdir = Path(os.environ.get("TMPDIR", tempfile.gettempdir()))
         caseattr_id = queue_edit(
             cursor,
             directory=tmpdir,