about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2023-04-19 17:49:58 +0300
committerFrederick Muriuki Muriithi2023-04-19 18:07:19 +0300
commit735adc012cc5d974eb8eabb26eda6c483af0da1a (patch)
treefad7b75fd7df167fa859674653f2b93274d3ba35
parent2a7e8b7ffdf260a643c7fcd615c8982d3268347f (diff)
downloadgenenetwork3-735adc012cc5d974eb8eabb26eda6c483af0da1a.tar.gz
oauth2: Link the phenotype traits to user groups.
-rw-r--r--gn3/auth/authorisation/data/phenotypes.py69
-rw-r--r--gn3/auth/authorisation/data/views.py29
-rw-r--r--scripts/search_phenotypes.py2
3 files changed, 95 insertions, 5 deletions
diff --git a/gn3/auth/authorisation/data/phenotypes.py b/gn3/auth/authorisation/data/phenotypes.py
index a330d4a..ff98295 100644
--- a/gn3/auth/authorisation/data/phenotypes.py
+++ b/gn3/auth/authorisation/data/phenotypes.py
@@ -1,11 +1,14 @@
 """Handle linking of Phenotype data to the Auth(entic|oris)ation system."""
+import uuid
 from typing import Any, Iterable
 
 from MySQLdb.cursors import DictCursor
 
 import gn3.auth.db as authdb
 import gn3.db_utils as gn3db
+from gn3.auth.dictify import dictify
 from gn3.auth.authorisation.checks import authorised_p
+from gn3.auth.authorisation.groups.models import Group
 
 def linked_phenotype_data(
         authconn: authdb.DbConnection, gn3conn: gn3db.Connection,
@@ -19,9 +22,9 @@ def linked_phenotype_data(
                        for row in authcursor.fetchall())
         if len(linked) <= 0:
             return iter(())
-        paramstr = "".join(["(%s, %s, %s, %s)"] * len(linked))
+        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(linked))
         query = (
-            "SELECT spc.SpeciesId, spc.SpeciesName, iset.InbredSetId, "
+            "SELECT spc.SpeciesId, spc.Name AS SpeciesName, iset.InbredSetId, "
             "iset.InbredSetName, pf.Id AS PublishFreezeId, "
             "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
             "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
@@ -35,10 +38,11 @@ def linked_phenotype_data(
             "ON pf.InbredSetId=pxr.InbredSetId") + (
                 " WHERE" if (len(linked) > 0 or bool(species)) else "") + (
                     (" (spc.SpeciesId, iset.InbredSetId, pf.Id, pxr.Id) "
-                     f"NOT IN ({paramstr})") if len(linked) > 0 else "") + (
+                     f"IN ({paramstr})") if len(linked) > 0 else "") + (
                         " AND"if len(linked) > 0 else "") + (
                         " spc.SpeciesName=%s" if bool(species) else "")
-        params = linked + ((species,) if bool(species) else tuple())
+        params = tuple(item for sublist in linked for item in sublist) + (
+            (species,) if bool(species) else tuple())
         gn3cursor.execute(query, params)
         return (item for item in gn3cursor.fetchall())
 
@@ -77,3 +81,60 @@ def ungrouped_phenotype_data(
         return tuple(dict(row) for row in cursor.fetchall())
 
     return tuple()
+
+def __traits__(gn3conn: gn3db.Connection, params: tuple[dict, ...]) -> tuple[dict, ...]:
+    """An internal utility function. Don't use outside of this module."""
+    if len(params) < 1:
+        return tuple()
+    paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(params))
+    with gn3conn.cursor(DictCursor) as cursor:
+        cursor.execute(
+            "SELECT spc.SpeciesId, iset.InbredSetId, pf.Id AS PublishFreezeId, "
+            "pf.Name AS dataset_name, pf.FullName AS dataset_fullname, "
+            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
+            "FROM "
+            "Species AS spc "
+            "INNER JOIN InbredSet AS iset "
+            "ON spc.SpeciesId=iset.SpeciesId "
+            "INNER JOIN PublishFreeze AS pf "
+            "ON iset.InbredSetId=pf.InbredSetId "
+            "INNER JOIN PublishXRef AS pxr "
+            "ON pf.InbredSetId=pxr.InbredSetId "
+            "WHERE (spc.SpeciesName, iset.InbredSetName, pf.Name, pxr.Id) "
+            f"IN ({paramstr})",
+            tuple(
+                itm for sublist in (
+                    (item["species"], item["group"], item["dataset"], item["name"])
+                    for item in params)
+                for itm in sublist))
+        return cursor.fetchall()
+
+@authorised_p(("system:data:link-to-group",),
+              error_description=(
+                  "You do not have sufficient privileges to link data to (a) "
+                  "group(s)."),
+              oauth2_scope="profile group resource")
+def link_phenotype_data(
+        authconn:authdb.DbConnection, gn3conn: gn3db.Connection, group: Group,
+        traits: tuple[dict, ...]) -> dict:
+    """Link phenotype traits to a user group."""
+    with authdb.cursor(authconn) as cursor:
+        params = tuple({
+            "data_link_id": str(uuid.uuid4()),
+            "group_id": str(group.group_id),
+            **item
+        } for item in __traits__(gn3conn, traits))
+        cursor.executemany(
+            "INSERT INTO linked_phenotype_data "
+            "VALUES ("
+            ":data_link_id, :group_id, :SpeciesId, :InbredSetId, "
+            ":PublishFreezeId, :dataset_name, :dataset_fullname, "
+            ":dataset_shortname, :PublishXRefId"
+            ")",
+            params)
+        return {
+            "description": (
+                f"Successfully linked {len(traits)} traits to group."),
+            "group": dictify(group),
+            "traits": params
+        }
diff --git a/gn3/auth/authorisation/data/views.py b/gn3/auth/authorisation/data/views.py
index a9861d2..81b3e2f 100644
--- a/gn3/auth/authorisation/data/views.py
+++ b/gn3/auth/authorisation/data/views.py
@@ -28,6 +28,7 @@ from gn3.auth.authorisation.resources.models import (
 
 from gn3.auth.authentication.oauth2.resource_server import require_oauth
 
+from gn3.auth.authorisation.data.phenotypes import link_phenotype_data
 from gn3.auth.authorisation.data.mrna import link_mrna_data, ungrouped_mrna_data
 from gn3.auth.authorisation.data.genotypes import (
     link_genotype_data, ungrouped_genotype_data)
@@ -246,3 +247,31 @@ def link_mrna() -> Response:
 
     return jsonify(with_db_connection(
         partial(__link__, **__values__(request.json))))
+
+@data.route("/link/phenotype", methods=["POST"])
+def link_phenotype() -> Response:
+    """Link phenotype data to group."""
+    def __values__(form):
+        if not bool(form.get("species_name", "").strip()):
+            raise InvalidData("Expected 'species_name' not provided.")
+        if not bool(form.get("group_id")):
+            raise InvalidData("Expected 'group_id' not provided.",)
+        try:
+            _group_id = uuid.UUID(form.get("group_id"))
+        except TypeError as terr:
+            raise InvalidData("Expected a UUID for 'group_id' value.") from terr
+        if not bool(form.get("selected")):
+            raise InvalidData("Expected at least one dataset to be provided.")
+        return {
+            "group_id": uuid.UUID(form["group_id"]),
+            "traits": form["selected"]
+        }
+
+    with gn3db.database_connection(app.config["SQL_URI"]) as gn3conn:
+        def __link__(conn: db.DbConnection, group_id: uuid.UUID,
+                     traits: tuple[dict, ...]) -> dict:
+            return link_phenotype_data(
+                conn, gn3conn, group_by_id(conn, group_id), traits)
+
+        return jsonify(with_db_connection(
+            partial(__link__, **__values__(request.json))))
diff --git a/scripts/search_phenotypes.py b/scripts/search_phenotypes.py
index 1bab41c..38b992b 100644
--- a/scripts/search_phenotypes.py
+++ b/scripts/search_phenotypes.py
@@ -97,7 +97,7 @@ def search(# pylint: disable=[too-many-arguments, too-many-locals]
                 for item in json.loads(selected))
             linked = tuple(
                 (row["SpeciesName"], row["InbredSetName"], row["dataset_name"],
-                 row["PublishXRefId"])
+                 str(row["PublishXRefId"]))
                 for row in linked_phenotype_data(authconn, gn3conn, species))
             page = 1
             count = 0