about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn_auth/auth/authorisation/resources/models.py2
-rw-r--r--gn_auth/auth/authorisation/resources/phenotype.py68
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/__init__.py1
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/models.py142
-rw-r--r--gn_auth/auth/authorisation/resources/phenotypes/views.py75
-rw-r--r--gn_auth/auth/authorisation/resources/views.py2
6 files changed, 221 insertions, 69 deletions
diff --git a/gn_auth/auth/authorisation/resources/models.py b/gn_auth/auth/authorisation/resources/models.py
index d3df6dc..8d3cfc3 100644
--- a/gn_auth/auth/authorisation/resources/models.py
+++ b/gn_auth/auth/authorisation/resources/models.py
@@ -29,7 +29,7 @@ from .genotypes.models import (
     attach_resources_data as genotype_attach_resources_data,
     link_data_to_resource as genotype_link_data_to_resource,
     unlink_data_from_resource as genotype_unlink_data_from_resource)
-from .phenotype import (
+from .phenotypes.models import (
     resource_data as phenotype_resource_data,
     attach_resources_data as phenotype_attach_resources_data,
     link_data_to_resource as phenotype_link_data_to_resource,
diff --git a/gn_auth/auth/authorisation/resources/phenotype.py b/gn_auth/auth/authorisation/resources/phenotype.py
deleted file mode 100644
index 7005db3..0000000
--- a/gn_auth/auth/authorisation/resources/phenotype.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""Phenotype data resources functions and utilities."""
-import uuid
-from typing import Optional, Sequence
-
-import sqlite3
-
-import gn_auth.auth.db.sqlite3 as db
-
-from .base import Resource
-from .data import __attach_data__
-
-def resource_data(
-        cursor: db.DbCursor,
-        resource_id: uuid.UUID,
-        offset: int = 0,
-        limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a Phenotype resource"""
-    cursor.execute(
-        ("SELECT * FROM phenotype_resources AS pr "
-         "INNER JOIN linked_phenotype_data AS lpd "
-         "ON pr.data_link_id=lpd.data_link_id "
-         "WHERE pr.resource_id=?") + (
-             f" LIMIT {limit} OFFSET {offset}" if bool(limit) else ""),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def link_data_to_resource(
-        conn: db.DbConnection,
-        resource: Resource,
-        data_link_id: uuid.UUID) -> dict:
-    """Link Phenotype data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO phenotype_resources VALUES"
-            "(:resource_id, :data_link_id)",
-            params)
-        return params
-
-def unlink_data_from_resource(
-        conn: db.DbConnection,
-        resource: Resource,
-        data_link_id: uuid.UUID) -> dict:
-    """Unlink data from Phenotype resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM phenotype_resources "
-                       "WHERE resource_id=? AND data_link_id=?",
-                       (str(resource.resource_id), str(data_link_id)))
-        return {
-            "resource_id": str(resource.resource_id),
-            "dataset_type": resource.resource_category.resource_category_key,
-            "data_link_id": str(data_link_id)
-        }
-
-def attach_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to Phenotype resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM phenotype_resources AS pr "
-        "INNER JOIN linked_phenotype_data AS lpd "
-        "ON pr.data_link_id=lpd.data_link_id "
-        f"WHERE pr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/__init__.py b/gn_auth/auth/authorisation/resources/phenotypes/__init__.py
new file mode 100644
index 0000000..0d4dbfa
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/__init__.py
@@ -0,0 +1 @@
+"""The phenotypes package."""
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/models.py b/gn_auth/auth/authorisation/resources/phenotypes/models.py
new file mode 100644
index 0000000..d4a516a
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/models.py
@@ -0,0 +1,142 @@
+"""Phenotype data resources functions and utilities."""
+import uuid
+from functools import reduce
+from typing import Optional, Sequence
+
+import sqlite3
+from pymonad.maybe import Just, Maybe, Nothing
+from pymonad.tools import monad_from_none_or_value
+
+import gn_auth.auth.db.sqlite3 as db
+from gn_auth.auth.authorisation.resources.data import __attach_data__
+from gn_auth.auth.authorisation.resources.base import Resource, resource_from_dbrow
+
+def resource_data(
+        cursor: db.DbCursor,
+        resource_id: uuid.UUID,
+        offset: int = 0,
+        limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
+    """Fetch data linked to a Phenotype resource"""
+    cursor.execute(
+        ("SELECT * FROM phenotype_resources AS pr "
+         "INNER JOIN linked_phenotype_data AS lpd "
+         "ON pr.data_link_id=lpd.data_link_id "
+         "WHERE pr.resource_id=?") + (
+             f" LIMIT {limit} OFFSET {offset}" if bool(limit) else ""),
+        (str(resource_id),))
+    return cursor.fetchall()
+
+def link_data_to_resource(
+        conn: db.DbConnection,
+        resource: Resource,
+        data_link_id: uuid.UUID) -> dict:
+    """Link Phenotype data with a resource."""
+    with db.cursor(conn) as cursor:
+        params = {
+            "resource_id": str(resource.resource_id),
+            "data_link_id": str(data_link_id)
+        }
+        cursor.execute(
+            "INSERT INTO phenotype_resources VALUES"
+            "(:resource_id, :data_link_id)",
+            params)
+        return params
+
+def unlink_data_from_resource(
+        conn: db.DbConnection,
+        resource: Resource,
+        data_link_id: uuid.UUID) -> dict:
+    """Unlink data from Phenotype resources"""
+    with db.cursor(conn) as cursor:
+        cursor.execute("DELETE FROM phenotype_resources "
+                       "WHERE resource_id=? AND data_link_id=?",
+                       (str(resource.resource_id), str(data_link_id)))
+        return {
+            "resource_id": str(resource.resource_id),
+            "dataset_type": resource.resource_category.resource_category_key,
+            "data_link_id": str(data_link_id)
+        }
+
+def attach_resources_data(
+        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+    """Attach linked data to Phenotype resources"""
+    placeholders = ", ".join(["?"] * len(resources))
+    cursor.execute(
+        "SELECT * FROM phenotype_resources AS pr "
+        "INNER JOIN linked_phenotype_data AS lpd "
+        "ON pr.data_link_id=lpd.data_link_id "
+        f"WHERE pr.resource_id IN ({placeholders})",
+        tuple(str(resource.resource_id) for resource in resources))
+    return __attach_data__(cursor.fetchall(), resources)
+
+
+def individual_linked_resource(
+        conn: db.DbConnection,
+        species_id: int,
+        population_id: int,
+        dataset_id: int,
+        xref_id: str) -> Maybe:
+    """Given the data details, return the linked resource, if one is defined."""
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "SELECT "
+            "rsc.*, rc.*, lpd.SpeciesId AS species_id, "
+            "lpd.InbredSetId AS population_id, lpd.PublishXRefId AS xref_id, "
+            "lpd.dataset_name, lpd.dataset_fullname, lpd.dataset_shortname "
+            "FROM linked_phenotype_data AS lpd "
+            "INNER JOIN phenotype_resources AS pr "
+            "ON lpd.data_link_id=pr.data_link_id "
+            "INNER JOIN resources AS rsc ON pr.resource_id=rsc.resource_id "
+            "INNER JOIN resource_categories AS rc "
+            "ON rsc.resource_category_id=rc.resource_category_id "
+            "WHERE "
+            "(lpd.SpeciesId, lpd.InbredSetId, lpd.PublishFreezeId, lpd.PublishXRefId) = "
+            "(?, ?, ?, ?)",
+            (species_id, population_id, dataset_id, xref_id))
+        return monad_from_none_or_value(
+            Nothing, Just, cursor.fetchone()).then(resource_from_dbrow)
+
+
+def all_linked_resources(
+        conn: db.DbConnection,
+        species_id: int,
+        population_id: int,
+        dataset_id: int) -> Maybe:
+    """Given the data details, return the linked resource, if one is defined."""
+    with db.cursor(conn) as cursor:
+        cursor.execute(
+            "SELECT rsc.*, rc.resource_category_key, "
+            "rc.resource_category_description, lpd.SpeciesId AS species_id, "
+            "lpd.InbredSetId AS population_id, lpd.PublishXRefId AS xref_id, "
+            "lpd.dataset_name, lpd.dataset_fullname, lpd.dataset_shortname "
+            "FROM linked_phenotype_data AS lpd "
+            "INNER JOIN phenotype_resources AS pr "
+            "ON lpd.data_link_id=pr.data_link_id INNER JOIN resources AS rsc "
+            "ON pr.resource_id=rsc.resource_id "
+            "INNER JOIN resource_categories AS rc "
+            "ON rsc.resource_category_id=rc.resource_category_id "
+            "WHERE "
+            "(lpd.SpeciesId, lpd.InbredSetId, lpd.PublishFreezeId) = (?, ?, ?)",
+            (species_id, population_id, dataset_id))
+
+        _rscdatakeys = (
+            "species_id", "population_id", "xref_id", "dataset_name",
+            "dataset_fullname", "dataset_shortname")
+        def __organise__(resources, row):
+            _rscid = uuid.UUID(row["resource_id"])
+            _resource = resources.get(_rscid, resource_from_dbrow(row))
+            return {
+                **resources,
+                _rscid: Resource(
+                    _resource.resource_id,
+                    _resource.resource_name,
+                    _resource.resource_category,
+                    _resource.public,
+                    _resource.resource_data + (
+                             {key: row[key] for key in _rscdatakeys},))
+            }
+        results: dict[uuid.UUID, Resource] = reduce(
+            __organise__, cursor.fetchall(), {})
+        if len(results) == 0:
+            return Nothing
+        return Just(tuple(results.values()))
diff --git a/gn_auth/auth/authorisation/resources/phenotypes/views.py b/gn_auth/auth/authorisation/resources/phenotypes/views.py
new file mode 100644
index 0000000..a971d2b
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotypes/views.py
@@ -0,0 +1,75 @@
+"""Views for the phenotype resources."""
+from pymonad.either import Left, Right
+from flask import jsonify, Blueprint, current_app as app
+
+from gn_auth.auth.db import sqlite3 as db
+from gn_auth.auth.requests import request_json
+from gn_auth.auth.authorisation.resources.request_utils import check_form
+from gn_auth.auth.authorisation.roles.models import user_roles_on_resource
+
+from gn_auth.auth.authentication.oauth2.resource_server import require_oauth
+
+from .models import all_linked_resources, individual_linked_resource
+
+phenobp = Blueprint("phenotypes", __name__)
+
+@phenobp.route("/phenotypes/individual/linked-resource", methods=["POST"])
+def get_individual_linked_resource():
+    """Get the linked resource for a particular phenotype within the dataset.
+
+    Phenotypes are a tad tricky. Each phenotype could technically be a resource
+    on its own, and thus a user could have access to only a subset of phenotypes
+    within the entire dataset."""
+    with (require_oauth.acquire("profile group resource") as _token,
+          db.connection(app.config["AUTH_DB"]) as conn):
+        return check_form(
+            request_json(),
+            "species_id",
+            "population_id",
+            "dataset_id",
+            "xref_id"
+        ).then(
+            lambda formdata: individual_linked_resource(
+                    conn,
+                    int(formdata["species_id"]),
+                    int(formdata["population_id"]),
+                    int(formdata["dataset_id"]),
+                    formdata["xref_id"]
+                ).maybe(Left("No linked resource!"),
+                        lambda lrsc: Right({
+                            "formdata": formdata,
+                            "resource": lrsc
+                        }))
+        ).then(
+            lambda fdlrsc: {
+                **fdlrsc,
+                "roles": user_roles_on_resource(
+                    conn, _token.user.user_id, fdlrsc["resource"].resource_id)
+            }
+        ).either(lambda error: (jsonify(error), 400),
+                 lambda res: jsonify({
+                     key: value for key, value in res.items()
+                     if key != "formdata"
+                 }))
+
+
+@phenobp.route("/phenotypes/linked-resources", methods=["POST"])
+def get_all_linked_resources():
+    """Get all the linked resources for all phenotypes within a dataset.
+
+    See `get_individual_linked_resource(…)` documentation."""
+    with (require_oauth.acquire("profile group resource") as _token,
+          db.connection(app.config["AUTH_DB"]) as conn):
+        return check_form(
+            request_json(),
+            "species_id",
+            "population_id",
+            "dataset_id"
+        ).then(
+            lambda formdata: all_linked_resources(
+                conn,
+                int(formdata["species_id"]),
+                int(formdata["population_id"]),
+                int(formdata["dataset_id"])).maybe(
+                    Left("No linked resource!"), Right)
+        ).either(lambda error: (jsonify(error), 400), jsonify)
diff --git a/gn_auth/auth/authorisation/resources/views.py b/gn_auth/auth/authorisation/resources/views.py
index 31421f4..3d590a3 100644
--- a/gn_auth/auth/authorisation/resources/views.py
+++ b/gn_auth/auth/authorisation/resources/views.py
@@ -42,6 +42,7 @@ from gn_auth.auth.authentication.users import User, user_by_id, user_by_email
 from .checks import authorised_for
 from .inbredset.views import popbp
 from .genotypes.views import genobp
+from .phenotypes.views import phenobp
 from .errors import MissingGroupError
 from .groups.models import Group, user_group
 from .models import (
@@ -54,6 +55,7 @@ from .models import (
 resources = Blueprint("resources", __name__)
 resources.register_blueprint(popbp, url_prefix="/")
 resources.register_blueprint(genobp, url_prefix="/")
+resources.register_blueprint(phenobp, url_prefix="/")
 
 @resources.route("/categories", methods=["GET"])
 @require_oauth("profile group resource")