Skip to content

Commit 6592b21

Browse files
committed
Add participant phenotypes to graphql
1 parent e94a4d8 commit 6592b21

File tree

3 files changed

+71
-30
lines changed

3 files changed

+71
-30
lines changed

api/graphql/loaders.py

+27-11
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@
1313
from db.python.connect import NotFoundError
1414
from db.python.layers import (
1515
AnalysisLayer,
16-
SampleLayer,
1716
AssayLayer,
17+
FamilyLayer,
1818
ParticipantLayer,
19+
SampleLayer,
1920
SequencingGroupLayer,
20-
FamilyLayer,
2121
)
2222
from db.python.tables.analysis import AnalysisFilter
2323
from db.python.tables.assay import AssayFilter
2424
from db.python.tables.project import ProjectPermissionsTable
2525
from db.python.tables.sample import SampleFilter
2626
from db.python.tables.sequencing_group import SequencingGroupFilter
27-
from db.python.utils import ProjectId, GenericFilter
27+
from db.python.utils import GenericFilter, ProjectId
2828
from models.models import (
29-
AssayInternal,
30-
SampleInternal,
31-
SequencingGroupInternal,
3229
AnalysisInternal,
33-
ParticipantInternal,
30+
AssayInternal,
3431
FamilyInternal,
32+
ParticipantInternal,
3533
Project,
34+
SampleInternal,
35+
SequencingGroupInternal,
3636
)
3737

3838

@@ -53,6 +53,8 @@ class LoaderKeys(enum.Enum):
5353
SAMPLES_FOR_PARTICIPANTS = 'samples_for_participants'
5454
SAMPLES_FOR_PROJECTS = 'samples_for_projects'
5555

56+
PHENOTYPES_FOR_PARTICIPANTS = 'phenotypes_for_participants'
57+
5658
PARTICIPANTS_FOR_IDS = 'participants_for_ids'
5759
PARTICIPANTS_FOR_FAMILIES = 'participants_for_families'
5860
PARTICIPANTS_FOR_PROJECTS = 'participants_for_projects'
@@ -291,9 +293,7 @@ async def load_participants_for_ids(
291293
p_by_id = {p.id: p for p in persons}
292294
missing_pids = set(participant_ids) - set(p_by_id.keys())
293295
if missing_pids:
294-
raise NotFoundError(
295-
f'Could not find participants with ids {missing_pids}'
296-
)
296+
raise NotFoundError(f'Could not find participants with ids {missing_pids}')
297297
return [p_by_id.get(p) for p in participant_ids]
298298

299299

@@ -400,7 +400,23 @@ async def load_analyses_for_sequencing_groups(
400400
return by_sg_id
401401

402402

403-
async def get_context(request: Request, connection=get_projectless_db_connection): # pylint: disable=unused-argument
403+
@connected_data_loader(LoaderKeys.PHENOTYPES_FOR_PARTICIPANTS)
404+
async def load_phenotypes_for_participants(
405+
participant_ids: list[int], connection
406+
) -> list[list[dict]]:
407+
"""
408+
Data loader for phenotypes for participants
409+
"""
410+
player = ParticipantLayer(connection)
411+
participant_phenotypes = await player.get_phenotypes_for_participants(
412+
participant_ids=participant_ids
413+
)
414+
return [participant_phenotypes.get(pid, {}) for pid in participant_ids]
415+
416+
417+
async def get_context(
418+
request: Request, connection=get_projectless_db_connection
419+
): # pylint: disable=unused-argument
404420
"""Get loaders / cache context for strawberyy GraphQL"""
405421
mapped_loaders = {k: fn(connection) for k, fn in loaders.items()}
406422
return {

api/graphql/schema.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,10 @@
1414
from strawberry.fastapi import GraphQLRouter
1515
from strawberry.types import Info
1616

17-
from api.graphql.filters import (
18-
GraphQLFilter,
19-
GraphQLMetaFilter,
20-
)
21-
from api.graphql.loaders import (
22-
get_context,
23-
LoaderKeys,
24-
)
17+
from api.graphql.filters import GraphQLFilter, GraphQLMetaFilter
18+
from api.graphql.loaders import LoaderKeys, get_context
2519
from db.python import enum_tables
26-
from db.python.layers import AnalysisLayer, SequencingGroupLayer, SampleLayer
20+
from db.python.layers import AnalysisLayer, SampleLayer, SequencingGroupLayer
2721
from db.python.layers.assay import AssayLayer
2822
from db.python.layers.family import FamilyLayer
2923
from db.python.tables.analysis import AnalysisFilter
@@ -34,21 +28,19 @@
3428
from db.python.utils import GenericFilter
3529
from models.enums import AnalysisStatus
3630
from models.models import (
37-
SampleInternal,
38-
ParticipantInternal,
39-
Project,
4031
AnalysisInternal,
32+
AssayInternal,
4133
FamilyInternal,
34+
ParticipantInternal,
35+
Project,
36+
SampleInternal,
4237
SequencingGroupInternal,
43-
AssayInternal,
4438
)
4539
from models.models.sample import sample_id_transform_to_raw
46-
from models.utils.sample_id_format import (
47-
sample_id_format,
48-
)
40+
from models.utils.sample_id_format import sample_id_format
4941
from models.utils.sequencing_group_id_format import (
50-
sequencing_group_id_transform_to_raw,
5142
sequencing_group_id_format,
43+
sequencing_group_id_transform_to_raw,
5244
)
5345

5446
enum_methods = {}
@@ -336,6 +328,13 @@ async def samples(
336328
samples = await info.context[LoaderKeys.SAMPLES_FOR_PARTICIPANTS].load(q)
337329
return [GraphQLSample.from_internal(s) for s in samples]
338330

331+
@strawberry.field
332+
async def phenotypes(
333+
self, info: Info, root: 'GraphQLParticipant'
334+
) -> strawberry.scalars.JSON:
335+
loader = info.context[LoaderKeys.PHENOTYPES_FOR_PARTICIPANTS]
336+
return await loader.load(root.id)
337+
339338
@strawberry.field
340339
async def families(
341340
self, info: Info, root: 'GraphQLParticipant'

db/python/layers/participant.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import re
33
from collections import defaultdict
44
from enum import Enum
5-
from typing import Dict, List, Tuple, Optional, Any
5+
from typing import Any, Dict, List, Optional, Tuple
66

7-
from db.python.connect import NotFoundError, NoOpAenter
7+
from db.python.connect import NoOpAenter, NotFoundError
88
from db.python.layers.base import BaseLayer
99
from db.python.layers.sample import SampleLayer
1010
from db.python.tables.family import FamilyTable
@@ -335,6 +335,21 @@ async def fill_in_missing_participants(self):
335335

336336
return f'Updated {len(sample_ids_to_update)} records'
337337

338+
async def insert_participant_phenotypes(
339+
self, participant_phenotypes: dict[int, dict]
340+
):
341+
"""
342+
Insert participant phenotypes, with format: {pid: {key: value}}
343+
"""
344+
ppttable = ParticipantPhenotypeTable(self.connection)
345+
return await ppttable.add_key_value_rows(
346+
[
347+
(pid, pk, pv)
348+
for pid, phenotypes in participant_phenotypes.items()
349+
for pk, pv in phenotypes.items()
350+
]
351+
)
352+
338353
async def generic_individual_metadata_importer(
339354
self,
340355
headers: List[str],
@@ -653,6 +668,17 @@ async def update_many_participant_external_ids(
653668

654669
# region PHENOTYPES / SEQR
655670

671+
async def get_phenotypes_for_participants(
672+
self, participant_ids: list[int]
673+
) -> dict[int, dict[str, Any]]:
674+
"""
675+
Get phenotypes for participants keyed by by pid
676+
"""
677+
ppttable = ParticipantPhenotypeTable(self.connection)
678+
return await ppttable.get_key_value_rows_for_participant_ids(
679+
participant_ids=participant_ids
680+
)
681+
656682
async def get_seqr_individual_template(
657683
self,
658684
project: int,

0 commit comments

Comments
 (0)