Skip to content

Commit

Permalink
Merge pull request #53 from dglemos/add/endpoint_update_mechanism
Browse files Browse the repository at this point in the history
Add endpoint to update molecular mechanism
  • Loading branch information
seeta-ramaraju authored Oct 30, 2024
2 parents 08a17c5 + 1303576 commit f871e42
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
LGDVariantConsequenceListSerializer, LGDVariantGenCCConsequenceSerializer,
LGDCrossCuttingModifierListSerializer, LGDCrossCuttingModifierSerializer,
LGDVariantTypeListSerializer, LGDVariantTypeSerializer,
LGDVariantTypeDescriptionListSerializer, LGDVariantTypeDescriptionSerializer)
LGDVariantTypeDescriptionListSerializer, LGDVariantTypeDescriptionSerializer,
MolecularMechanismSerializer)

from .stable_id import G2PStableIDSerializer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,9 +452,15 @@ def update(self, instance, validated_data):
"""
# validated_data example:
# {'confidence': {'value': 'definitive'}, 'confidence_support': '', 'is_reviewed': None}
confidence = validated_data.get("confidence")["value"]
confidence_support = validated_data.get("confidence_support")
is_reviewed = validated_data.get("is_reviewed")
validated_confidence = validated_data.get("confidence", None)
confidence_support = validated_data.get("confidence_support", None)
is_reviewed = validated_data.get("is_reviewed", None)

if(validated_confidence is not None and isinstance(validated_confidence, dict)
and "value" in validated_confidence):
confidence = validated_confidence["value"]
else:
raise serializers.ValidationError({"error": f"Empty confidence value"})

# Get confidence
try:
Expand Down Expand Up @@ -492,6 +498,143 @@ def update(self, instance, validated_data):

return instance

def update_mechanism(self, lgd_instance, validated_data):
"""
Method to update the molecular mechanism of the LGD record.
It only allows to update mechanisms with value 'undetermined'
and support value 'inferred'.
Mandatory fields are molecular_mechanism name and support (inferred/evidence).
If evidence is provided, the code expects the 'evidence_types' to be populated
otherwise the evidence data is not stored.
Example: "molecular_mechanism": {
"name": "gain of function",
"support": "evidence"
},
"mechanism_synopsis": {
"name": "",
"support": ""
},
"mechanism_evidence": [{'pmid': '25099252', 'description': 'text', 'evidence_types':
[{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]}]
"""
molecular_mechanism_value = validated_data["molecular_mechanism"]["name"] # the mechanism value
molecular_mechanism_support = validated_data["molecular_mechanism"]["support"] # the mechanism support (inferred/evidence)
mechanism_synopsis = validated_data.get("mechanism_synopsis", None) # mechanism synopsis is optional
mechanism_evidence = validated_data.get("mechanism_evidence", None) # molecular mechanism evidence is optional

try:
cv_mechanism_obj = CVMolecularMechanism.objects.get(
value = molecular_mechanism_value,
type = "mechanism"
)
except CVMolecularMechanism.DoesNotExist:
raise serializers.ValidationError({"message": f"Invalid mechanism value '{molecular_mechanism_value}'"})

try:
cv_support_obj = CVMolecularMechanism.objects.get(
value = molecular_mechanism_support,
type = "support"
)
except CVMolecularMechanism.DoesNotExist:
raise serializers.ValidationError({"message": f"Invalid mechanism support '{molecular_mechanism_support}'"})

# The mechanism synopsis is optional
cv_synopsis_obj = None
cv_synopsis_support_obj = None
if mechanism_synopsis is not None and mechanism_synopsis.get("name", "") != "":
mechanism_synopsis_value = mechanism_synopsis.get("name")
mechanism_synopsis_support = mechanism_synopsis.get("support")

try:
cv_synopsis_obj = CVMolecularMechanism.objects.get(
value = mechanism_synopsis_value,
type = "mechanism_synopsis"
)
except CVMolecularMechanism.DoesNotExist:
raise serializers.ValidationError({"message": f"Invalid mechanism synopsis value '{mechanism_synopsis_value}'"})

try:
cv_synopsis_support_obj = CVMolecularMechanism.objects.get(
value = mechanism_synopsis_support,
type = "support"
)
except CVMolecularMechanism.DoesNotExist:
raise serializers.ValidationError({"message": f"Invalid mechanism synopsis support '{mechanism_synopsis_support}'"})

# Insert mechanism
mechanism_obj = MolecularMechanism.objects.create(
mechanism = cv_mechanism_obj,
mechanism_support = cv_support_obj,
synopsis = cv_synopsis_obj,
synopsis_support = cv_synopsis_support_obj,
is_deleted = 0
)

# Get evidence - the mechanism evidence was validated in the view 'LGDUpdateMechanism'
# Example: {'pmid': '25099252', 'description': 'text', 'evidence_types':
# [{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]}
for evidence in mechanism_evidence:
pmid = evidence.get("pmid")

# Check if the PMID exists in G2P
# When updating the mechanism the supporting pmid used as evidence
# have to already be linked to the LGD record
try:
publication_obj = Publication.objects.get(pmid=pmid)
except Publication.DoesNotExist:
# TODO: improve in future to insert new pmids + link them to the record
raise serializers.ValidationError({"message": f"pmid '{pmid}' not found in G2P"})

if evidence.get("description") != "":
description = evidence.get("description")
else:
description = None

evidence_types = evidence.get("evidence_types")
for evidence_type in evidence_types:
# primary_type is the evidence subtype ('rescue')
primary_type = evidence_type.get("primary_type", None)
if not primary_type:
raise serializers.ValidationError({"message": f"Empty evidence subtype"})
primary_type = primary_type.lower()
# secondary_type is the evidence value ('human')
secondary_type = evidence_type.get("secondary_type")
for m_type in secondary_type:
try:
cv_evidence_obj = CVMolecularMechanism.objects.get(
value = m_type.lower(),
type = "evidence",
subtype = primary_type
)
except CVMolecularMechanism.DoesNotExist:
raise serializers.ValidationError({"message": f"Invalid mechanism evidence '{m_type}'"})

# Insert evidence
mechanism_evidence_obj = MolecularMechanismEvidence.objects.create(
molecular_mechanism = mechanism_obj,
description = description,
publication = publication_obj,
evidence = cv_evidence_obj,
is_deleted = 0
)

# Save old molecular mechanism to delete it later
old_mechanism_obj = lgd_instance.molecular_mechanism

# Update LGD record
lgd_instance.molecular_mechanism = mechanism_obj
lgd_instance.date_review = datetime.now()
lgd_instance.save()

# The old molecular mechanism can be deleted - the deletion is stored in the history table
# As this method only allows to update 'undetermined' mechanisms, there is no evidence to be deleted
old_mechanism_obj.delete()

return lgd_instance

class Meta:
model = LocusGenotypeDisease
exclude = ['id', 'is_deleted', 'date_review']
Expand Down
2 changes: 2 additions & 0 deletions gene2phenotype_project/gene2phenotype_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def perform_create(self, serializer):

### Endpoints to update/add/delete the G2P record (LGD) ###
path('lgd/<str:stable_id>/update_confidence/', views.LGDUpdateConfidence.as_view(), name="lgd_update_confidence"),
# Update molecular mechanism - only allows to update if mechanism is 'undetermined' and support is 'inferred'
path('lgd/<str:stable_id>/update_mechanism/', views.LGDUpdateMechanism.as_view(), name="lgd_update_mechanism"),
# Add or delete panel from LGD record. Actions: UPDATE (to delete one panel), POST (to add one panel)
path('lgd/<str:stable_id>/panel/', views.LGDEditPanel.as_view(), name="lgd_panel"),
# Add or delete publication(s) from LGD record. Actions: UPDATE (to delete one publication), POST (to add multiple publications)
Expand Down
3 changes: 2 additions & 1 deletion gene2phenotype_project/gene2phenotype_app/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
LocusGenotypeDiseaseDetail, LGDEditCCM,
LGDEditComment, LGDEditVariantConsequences,
LGDEditVariantTypes, LGDEditVariantTypeDescriptions,
LGDUpdateConfidence, LocusGenotypeDiseaseDelete)
LGDUpdateConfidence, LocusGenotypeDiseaseDelete,
LGDUpdateMechanism)

from .phenotype import AddPhenotype, PhenotypeDetail, LGDEditPhenotypes, LGDEditPhenotypeSummary
12 changes: 11 additions & 1 deletion gene2phenotype_project/gene2phenotype_app/views/base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from rest_framework import generics, status
from django.core.exceptions import PermissionDenied
from django.http import Http404
from rest_framework.response import Response
from django.db import transaction
from django.urls import get_resolver
from rest_framework.decorators import api_view
from gene2phenotype_app.models import User

import re

from gene2phenotype_app.models import User



class BaseView(generics.ListAPIView):
"""
Expand Down Expand Up @@ -48,6 +52,12 @@ def handle_no_permission(self, data, stable_id):
raise Http404(f"{data}")
else:
raise Http404(f"Could not find '{data}' for ID '{stable_id}'")

def handle_no_update(self, data, stable_id):
if data is None:
raise Http404(f"{data}")
else:
raise PermissionDenied(f"Cannot update '{data}' for ID '{stable_id}'")


@api_view(['GET'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
LGDCommentSerializer, LGDVariantConsequenceListSerializer,
LGDVariantGenCCConsequenceSerializer, LGDCrossCuttingModifierListSerializer,
LGDVariantTypeListSerializer, LGDVariantTypeSerializer,
LGDVariantTypeDescriptionListSerializer, LGDVariantTypeDescriptionSerializer)
LGDVariantTypeDescriptionListSerializer, LGDVariantTypeDescriptionSerializer,
MolecularMechanismSerializer)

from gene2phenotype_app.models import (User, Attrib, LocusGenotypeDisease, OntologyTerm,
G2PStableID, CVMolecularMechanism, LGDCrossCuttingModifier,
Expand Down Expand Up @@ -147,7 +148,7 @@ def list(self, request, *args, **kwargs):


### Add or delete data ###
class LGDUpdateConfidence(generics.UpdateAPIView):
class LGDUpdateConfidence(BaseUpdate):
http_method_names = ['put', 'options']
serializer_class = LocusGenotypeDiseaseSerializer
permission_classes = [permissions.IsAuthenticated]
Expand Down Expand Up @@ -218,6 +219,148 @@ def update(self, request, stable_id):
else:
return Response({"errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

class LGDUpdateMechanism(BaseUpdate):
http_method_names = ['patch', 'options']
serializer_class = MolecularMechanismSerializer # TO CHECK
permission_classes = [permissions.IsAuthenticated]

def get_queryset(self):
"""
Retrieves a queryset of LocusGenotypeDisease objects associated with a stable ID
for the authenticated user.
Args:
stable_id (str): The stable ID from the URL kwargs.
Returns:
QuerySet: A queryset of LocusGenotypeDisease objects.
Raises:
Http404: If the stable ID does not exist.
PermissionDenied: If update is not allowed.
"""
stable_id = self.kwargs['stable_id']

g2p_stable_id = get_object_or_404(G2PStableID, stable_id=stable_id)
# Get the record
queryset = LocusGenotypeDisease.objects.filter(stable_id=g2p_stable_id, is_deleted=0)

if not queryset.exists():
self.handle_no_permission('Entry', stable_id)
else:
# Only 'undetermined' mechanisms can be updated
lgd_obj = queryset.first()
if not (lgd_obj.molecular_mechanism.mechanism.value == "undetermined"
and lgd_obj.molecular_mechanism.mechanism_support.value == "inferred"):
self.handle_no_update('molecular mechanism', stable_id)

return queryset

def patch(self, request, stable_id):
"""
Partially updates the LGD record with a new molecular mechanism.
It only allows to update mechanisms with value 'undetermined'
and support 'inferred'.
Mandatory fields: "molecular_mechanism".
Supporting pmids have to already be linked to the LGD record.
Args:
request: new molecular mechanism data
stable_id (str): The stable ID to update.
Request example:
{
"molecular_mechanism": {
"name": "gain of function",
"support": "evidence"
},
"mechanism_synopsis": {
"name": "",
"support": ""
},
"mechanism_evidence": [{'pmid': '25099252', 'description': 'text', 'evidence_types':
[{'primary_type': 'Rescue', 'secondary_type': ['Human', 'Patient Cells']}]}]
}
"""
user = request.user
mechanism_data = request.data

# Validate mechanism data
molecular_mechanism = mechanism_data.get("molecular_mechanism", None)
mechanism_synopsis = mechanism_data.get("mechanism_synopsis", None) # optional
mechanism_evidence = mechanism_data.get("mechanism_evidence", None) # optional

# Check the request data format:
# the keys "molecular_mechanism", "mechanism_synopsis" and "mechanism_evidence"
# are mandatory but only "molecular_mechanism" has to be populated.
if molecular_mechanism is None:
return Response(
{"error": f"Molecular mechanism is missing"},
status=status.HTTP_400_BAD_REQUEST
)
if mechanism_synopsis is None:
return Response(
{"error": f"Molecular mechanism synopsis is missing"},
status=status.HTTP_400_BAD_REQUEST
)

# Get molecular mechanism - check if mandatory fields are populated (value and support)
molecular_mechanism_value = molecular_mechanism.get("name", None)
molecular_mechanism_support = molecular_mechanism.get("support", None)

if molecular_mechanism_value is None:
return Response(
{"error": f"Empty molecular mechanism value"},
status=status.HTTP_400_BAD_REQUEST
)

if molecular_mechanism_support is None:
return Response(
{"error": f"Empty molecular mechanism support"},
status=status.HTTP_400_BAD_REQUEST
)

if (mechanism_evidence is None or not mechanism_evidence) and molecular_mechanism_support == "evidence":
return Response(
{"error": f"Molecular mechanism evidence is missing"},
status=status.HTTP_400_BAD_REQUEST
)

# Get G2P entry to be updated
lgd_obj = self.get_queryset().first()

serializer = LocusGenotypeDiseaseSerializer()

# Check if user has permission to edit this entry
user_obj = get_object_or_404(User, email=user, is_active=1)
serializer_user = UserSerializer(user_obj, context={"user" : user})
user_panel_list = [panel for panel in serializer_user.panels_names(user_obj)]
has_common = serializer.check_user_permission(lgd_obj, user_panel_list)

if has_common is False:
return Response(
{"error": f"No permission to update record '{stable_id}'"},
status=status.HTTP_403_FORBIDDEN
)

# Separate method to update mechanism
# Updating the mechanism can be complex, specially if evidence data is provided
# To avoid problems with other LDG updates, the mechanism is going to be
# updated in a separate method - this implies extra validation
try:
serializer.update_mechanism(lgd_obj, mechanism_data)
except Exception as e:
return Response(
{"error": f"Error while updating molecular mechanism"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
else:
return Response(
{"message": f"Molecular mechanism updated successfully for '{stable_id}'"},
status=status.HTTP_200_OK)

class LGDEditVariantConsequences(APIView):
"""
Add or delete lgd-variant consequence(s).
Expand Down

0 comments on commit f871e42

Please sign in to comment.