about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn_auth/auth/authorisation/resources/__init__.py2
-rw-r--r--gn_auth/auth/authorisation/resources/data.py29
-rw-r--r--gn_auth/auth/authorisation/resources/genotype_resource.py70
-rw-r--r--gn_auth/auth/authorisation/resources/models.py238
-rw-r--r--gn_auth/auth/authorisation/resources/mrna_resource.py67
-rw-r--r--gn_auth/auth/authorisation/resources/phenotype_resource.py69
6 files changed, 274 insertions, 201 deletions
diff --git a/gn_auth/auth/authorisation/resources/__init__.py b/gn_auth/auth/authorisation/resources/__init__.py
index 869ab60..8037ca9 100644
--- a/gn_auth/auth/authorisation/resources/__init__.py
+++ b/gn_auth/auth/authorisation/resources/__init__.py
@@ -1,2 +1,2 @@
 """Initialise the `gn3.auth.authorisation.resources` package."""
-from .models import Resource, ResourceCategory
+from .base import Resource, ResourceCategory
diff --git a/gn_auth/auth/authorisation/resources/data.py b/gn_auth/auth/authorisation/resources/data.py
new file mode 100644
index 0000000..8f5e625
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/data.py
@@ -0,0 +1,29 @@
+"""
+Utilities for handling data on resources.
+
+These are mostly meant for internal use.
+"""
+from uuid import UUID
+from typing import Sequence
+from functools import reduce
+
+import sqlite3
+
+from .base import Resource
+
+def __attach_data__(
+        data_rows: Sequence[sqlite3.Row],
+        resources: Sequence[Resource]) -> Sequence[Resource]:
+    def __organise__(acc, row):
+        resource_id = UUID(row["resource_id"])
+        return {
+            **acc,
+            resource_id: acc.get(resource_id, tuple()) + (dict(row),)
+        }
+    organised: dict[UUID, tuple[dict, ...]] = reduce(__organise__, data_rows, {})
+    return tuple(
+        Resource(
+            resource.group, resource.resource_id, resource.resource_name,
+            resource.resource_category, resource.public,
+            organised.get(resource.resource_id, tuple()))
+        for resource in resources)
diff --git a/gn_auth/auth/authorisation/resources/genotype_resource.py b/gn_auth/auth/authorisation/resources/genotype_resource.py
new file mode 100644
index 0000000..03e2e68
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/genotype_resource.py
@@ -0,0 +1,70 @@
+"""Genotype 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 Genotype resource"""
+    cursor.execute(
+        (("SELECT * FROM genotype_resources AS gr "
+          "INNER JOIN linked_genotype_data AS lgd "
+          "ON gr.data_link_id=lgd.data_link_id "
+          "WHERE gr.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 Genotype data with a resource."""
+    with db.cursor(conn) as cursor:
+        params = {
+            "group_id": str(resource.group.group_id),
+            "resource_id": str(resource.resource_id),
+            "data_link_id": str(data_link_id)
+        }
+        cursor.execute(
+            "INSERT INTO genotype_resources VALUES"
+            "(:group_id, :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 Genotype resources"""
+    with db.cursor(conn) as cursor:
+        cursor.execute("DELETE FROM genotype_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": data_link_id
+        }
+
+def attach_resources_data(
+        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+    """Attach linked data to Genotype resources"""
+    placeholders = ", ".join(["?"] * len(resources))
+    cursor.execute(
+        "SELECT * FROM genotype_resources AS gr "
+        "INNER JOIN linked_genotype_data AS lgd "
+        "ON gr.data_link_id=lgd.data_link_id "
+        f"WHERE gr.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/models.py b/gn_auth/auth/authorisation/resources/models.py
index 5718753..b5a6cd5 100644
--- a/gn_auth/auth/authorisation/resources/models.py
+++ b/gn_auth/auth/authorisation/resources/models.py
@@ -1,6 +1,5 @@
 """Handle the management of resources."""
 import json
-import sqlite3
 from uuid import UUID, uuid4
 from functools import reduce, partial
 from typing import Dict, Sequence, Optional
@@ -17,17 +16,34 @@ from ..errors import NotFoundError, AuthorisationError
 from ..groups.models import (
     Group, GroupRole, user_group, group_by_id, is_group_leader)
 
+from .checks import authorised_for
+from .base import Resource, ResourceCategory
+from .mrna_resource import (
+    resource_data as mrna_resource_data,
+    attach_resources_data as mrna_attach_resources_data,
+    link_data_to_resource as mrna_link_data_to_resource,
+    unlink_data_from_resource as mrna_unlink_data_from_resource)
+from .genotype_resource import (
+    resource_data as genotype_resource_data,
+    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_resource 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,
+    unlink_data_from_resource as phenotype_unlink_data_from_resource)
+
 class MissingGroupError(AuthorisationError):
     """Raised for any resource operation without a group."""
 
-
-def __assign_resource_owner_role__(cursor, resource, user):
+def __assign_resource_owner_role__(cursor, resource, user, group):
     """Assign `user` the 'Resource Owner' role for `resource`."""
     cursor.execute(
         "SELECT gr.* FROM group_roles AS gr INNER JOIN roles AS r "
         "ON gr.role_id=r.role_id WHERE r.role_name='resource-owner' "
         "AND gr.group_id=?",
-        (str(resource.group.group_id),))
+        (str(group.group_id),))
     role = cursor.fetchone()
     if not role:
         cursor.execute("SELECT * FROM roles WHERE role_name='resource-owner'")
@@ -36,7 +52,7 @@ def __assign_resource_owner_role__(cursor, resource, user):
             "INSERT INTO group_roles VALUES "
             "(:group_role_id, :group_id, :role_id)",
             {"group_role_id": str(uuid4()),
-             "group_id": str(resource.group.group_id),
+             "group_id": str(group.group_id),
              "role_id": role["role_id"]})
 
     cursor.execute(
@@ -44,7 +60,7 @@ def __assign_resource_owner_role__(cursor, resource, user):
             "VALUES ("
             ":group_id, :user_id, :role_id, :resource_id"
             ")",
-            {"group_id": str(resource.group.group_id),
+            {"group_id": str(resource.group_id),
              "user_id": str(user.user_id),
              "role_id": role["role_id"],
              "resource_id": str(resource.resource_id)})
@@ -63,15 +79,17 @@ def create_resource(
         if not group:
             raise MissingGroupError(
                 "User with no group cannot create a resource.")
-        resource = Resource(
-            group, uuid4(), resource_name, resource_category, public)
+        resource = Resource(uuid4(), resource_name, resource_category, public)
         cursor.execute(
-            "INSERT INTO resources VALUES (?, ?, ?, ?, ?)",
-            (str(resource.group.group_id), str(resource.resource_id),
+            "INSERT INTO resources VALUES (?, ?, ?, ?)",
+            (str(resource.resource_id),
              resource_name,
              str(resource.resource_category.resource_category_id),
              1 if resource.public else 0))
-        __assign_resource_owner_role__(cursor, resource, user)
+        cursor.execute("INSERT INTO resource_ownership (group_id, resource_id) "
+                       "VALUES (?, ?)",
+                       (str(group.group_id), str(resource.resource_id)))
+        __assign_resource_owner_role__(cursor, resource, user, group)
 
     return resource
 
@@ -201,50 +219,6 @@ def attach_resource_data(cursor: db.DbCursor, resource: Resource) -> Resource:
         resource.group, resource.resource_id, resource.resource_name,
         resource.resource_category, resource.public, data_rows)
 
-def mrna_resource_data(cursor: db.DbCursor,
-                       resource_id: UUID,
-                       offset: int = 0,
-                       limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a mRNA resource"""
-    cursor.execute(
-        (("SELECT * FROM mrna_resources AS mr "
-          "INNER JOIN linked_mrna_data AS lmr "
-          "ON mr.data_link_id=lmr.data_link_id "
-          "WHERE mr.resource_id=?") + (
-              f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "")),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def genotype_resource_data(
-        cursor: db.DbCursor,
-        resource_id: UUID,
-        offset: int = 0,
-        limit: Optional[int] = None) -> Sequence[sqlite3.Row]:
-    """Fetch data linked to a Genotype resource"""
-    cursor.execute(
-        (("SELECT * FROM genotype_resources AS gr "
-          "INNER JOIN linked_genotype_data AS lgd "
-          "ON gr.data_link_id=lgd.data_link_id "
-          "WHERE gr.resource_id=?") + (
-              f" LIMIT {limit} OFFSET {offset}" if bool(limit) else "")),
-        (str(resource_id),))
-    return cursor.fetchall()
-
-def phenotype_resource_data(
-        cursor: db.DbCursor,
-        resource_id: 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 resource_by_id(
         conn: db.DbConnection, user: User, resource_id: UUID) -> Resource:
     """Retrieve a resource by its ID."""
@@ -268,51 +242,6 @@ def resource_by_id(
 
     raise NotFoundError(f"Could not find a resource with id '{resource_id}'")
 
-def __link_mrna_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link mRNA Assay data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO mrna_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
-def __link_geno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link Genotype data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO genotype_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
-def __link_pheno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Link Phenotype data with a resource."""
-    with db.cursor(conn) as cursor:
-        params = {
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            "data_link_id": str(data_link_id)
-        }
-        cursor.execute(
-            "INSERT INTO phenotype_resources VALUES"
-            "(:group_id, :resource_id, :data_link_id)",
-            params)
-        return params
-
 def link_data_to_resource(
         conn: db.DbConnection, user: User, resource_id: UUID, dataset_type: str,
         data_link_id: UUID) -> dict:
@@ -327,50 +256,11 @@ def link_data_to_resource(
     resource = with_db_connection(partial(
         resource_by_id, user=user, resource_id=resource_id))
     return {
-        "mrna": __link_mrna_data_to_resource__,
-        "genotype": __link_geno_data_to_resource__,
-        "phenotype": __link_pheno_data_to_resource__,
+        "mrna": mrna_link_data_to_resource,
+        "genotype": genotype_link_data_to_resource,
+        "phenotype": phenotype_link_data_to_resource,
     }[dataset_type.lower()](conn, resource, data_link_id)
 
-def __unlink_mrna_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Unlink data from mRNA Assay resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM mrna_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": data_link_id
-        }
-
-def __unlink_geno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: UUID) -> dict:
-    """Unlink data from Genotype resources"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("DELETE FROM genotype_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": data_link_id
-        }
-
-def __unlink_pheno_data_to_resource__(
-        conn: db.DbConnection, resource: Resource, data_link_id: 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 unlink_data_from_resource(
         conn: db.DbConnection, user: User, resource_id: UUID, data_link_id: UUID):
     """Unlink data from resource."""
@@ -385,9 +275,9 @@ def unlink_data_from_resource(
         resource_by_id, user=user, resource_id=resource_id))
     dataset_type = resource.resource_category.resource_category_key
     return {
-        "mrna": __unlink_mrna_data_to_resource__,
-        "genotype": __unlink_geno_data_to_resource__,
-        "phenotype": __unlink_pheno_data_to_resource__,
+        "mrna": mrna_unlink_data_from_resource,
+        "genotype": genotype_unlink_data_from_resource,
+        "phenotype": phenotype_unlink_data_from_resource,
     }[dataset_type.lower()](conn, resource, data_link_id)
 
 def organise_resources_by_category(resources: Sequence[Resource]) -> dict[
@@ -401,66 +291,14 @@ def organise_resources_by_category(resources: Sequence[Resource]) -> dict[
         }
     return reduce(__organise__, resources, {})
 
-def __attach_data__(
-        data_rows: Sequence[sqlite3.Row],
-        resources: Sequence[Resource]) -> Sequence[Resource]:
-    def __organise__(acc, row):
-        resource_id = UUID(row["resource_id"])
-        return {
-            **acc,
-            resource_id: acc.get(resource_id, tuple()) + (dict(row),)
-        }
-    organised: dict[UUID, tuple[dict, ...]] = reduce(__organise__, data_rows, {})
-    return tuple(
-        Resource(
-            resource.group, resource.resource_id, resource.resource_name,
-            resource.resource_category, resource.public,
-            organised.get(resource.resource_id, tuple()))
-        for resource in resources)
-
-def attach_mrna_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to mRNA Assay resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM mrna_resources AS mr INNER JOIN linked_mrna_data AS lmd"
-        " ON mr.data_link_id=lmd.data_link_id "
-        f"WHERE mr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
-
-def attach_genotype_resources_data(
-        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
-    """Attach linked data to Genotype resources"""
-    placeholders = ", ".join(["?"] * len(resources))
-    cursor.execute(
-        "SELECT * FROM genotype_resources AS gr "
-        "INNER JOIN linked_genotype_data AS lgd "
-        "ON gr.data_link_id=lgd.data_link_id "
-        f"WHERE gr.resource_id IN ({placeholders})",
-        tuple(str(resource.resource_id) for resource in resources))
-    return __attach_data__(cursor.fetchall(), resources)
-
-def attach_phenotype_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 attach_resources_data(
         conn: db.DbConnection, resources: Sequence[Resource]) -> Sequence[
             Resource]:
     """Attach linked data for each resource in `resources`"""
     resource_data_function = {
-        "mrna": attach_mrna_resources_data,
-        "genotype": attach_genotype_resources_data,
-        "phenotype": attach_phenotype_resources_data
+        "mrna": mrna_attach_resources_data,
+        "genotype": genotype_attach_resources_data,
+        "phenotype": phenotype_attach_resources_data
     }
     organised = organise_resources_by_category(resources)
     with db.cursor(conn) as cursor:
diff --git a/gn_auth/auth/authorisation/resources/mrna_resource.py b/gn_auth/auth/authorisation/resources/mrna_resource.py
new file mode 100644
index 0000000..77295f3
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/mrna_resource.py
@@ -0,0 +1,67 @@
+"""mRNA 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 mRNA resource"""
+    cursor.execute(
+        (("SELECT * FROM mrna_resources AS mr "
+          "INNER JOIN linked_mrna_data AS lmr "
+          "ON mr.data_link_id=lmr.data_link_id "
+          "WHERE mr.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 mRNA Assay data with a resource."""
+    with db.cursor(conn) as cursor:
+        params = {
+            "group_id": str(resource.group.group_id),
+            "resource_id": str(resource.resource_id),
+            "data_link_id": str(data_link_id)
+        }
+        cursor.execute(
+            "INSERT INTO mrna_resources VALUES"
+            "(:group_id, :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 mRNA Assay resources"""
+    with db.cursor(conn) as cursor:
+        cursor.execute("DELETE FROM mrna_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": data_link_id
+        }
+
+def attach_resources_data(
+        cursor, resources: Sequence[Resource]) -> Sequence[Resource]:
+    """Attach linked data to mRNA Assay resources"""
+    placeholders = ", ".join(["?"] * len(resources))
+    cursor.execute(
+        "SELECT * FROM mrna_resources AS mr INNER JOIN linked_mrna_data AS lmd"
+        " ON mr.data_link_id=lmd.data_link_id "
+        f"WHERE mr.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/phenotype_resource.py b/gn_auth/auth/authorisation/resources/phenotype_resource.py
new file mode 100644
index 0000000..cf33579
--- /dev/null
+++ b/gn_auth/auth/authorisation/resources/phenotype_resource.py
@@ -0,0 +1,69 @@
+"""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 = {
+            "group_id": str(resource.group.group_id),
+            "resource_id": str(resource.resource_id),
+            "data_link_id": str(data_link_id)
+        }
+        cursor.execute(
+            "INSERT INTO phenotype_resources VALUES"
+            "(:group_id, :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)