diff --git a/api/__init__.py b/api/__init__.py index ea91f1d7..84660184 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -88,6 +88,7 @@ def create_app(): annotations_lookup_db.init_app(bar_app) eplant2_db.init_app(bar_app) eplant_poplar_db.init_app(bar_app) + eplant_rice_db.init_app(bar_app) eplant_tomato_db.init_app(bar_app) poplar_nssnp_db.init_app(bar_app) tomato_nssnp_db.init_app(bar_app) @@ -147,6 +148,7 @@ def create_app(): annotations_lookup_db = SQLAlchemy(metadata=MetaData()) eplant2_db = SQLAlchemy(metadata=MetaData()) eplant_poplar_db = SQLAlchemy(metadata=MetaData()) +eplant_rice_db = SQLAlchemy(metadata=MetaData()) eplant_tomato_db = SQLAlchemy(metadata=MetaData()) poplar_nssnp_db = SQLAlchemy(metadata=MetaData()) tomato_nssnp_db = SQLAlchemy(metadata=MetaData()) diff --git a/api/models/eplant_rice.py b/api/models/eplant_rice.py new file mode 100644 index 00000000..6bfe42b6 --- /dev/null +++ b/api/models/eplant_rice.py @@ -0,0 +1,9 @@ +from api import eplant_rice_db as db + + +class GeneAnnotation(db.Model): + __bind_key__ = "eplant_rice" + __tablename__ = "gene_annotation" + + gene = db.Column(db.String(20), nullable=False, primary_key=True) + annotation = db.Column(db.String(64000), nullable=False, primary_key=False) diff --git a/api/resources/gene_annotation.py b/api/resources/gene_annotation.py index 65f710ca..df27a912 100644 --- a/api/resources/gene_annotation.py +++ b/api/resources/gene_annotation.py @@ -1,10 +1,13 @@ -from flask_restx import Namespace, Resource +from flask_restx import Namespace, Resource, fields +from flask import request from markupsafe import escape from sqlalchemy.exc import OperationalError +from api.models.eplant_rice import GeneAnnotation as EplantRiceAnnotation from api.models.eplant_poplar import GeneAnnotation as EplantPoplarAnnotation from api.models.eplant_tomato import GeneAnnotation as EplantTomatoAnnotation from api.models.eplant2 import AgiAnnotation, TAIR10, GeneRIFs from api.utils.bar_utils import BARUtils +from marshmallow import Schema, ValidationError, fields as marshmallow_fields gene_annotation = Namespace( @@ -22,6 +25,7 @@ def get(self, query=""): annotation_db_list = { "tomato": EplantTomatoAnnotation, "poplar": EplantPoplarAnnotation, + "rice": EplantRiceAnnotation, "arabidopsis": [AgiAnnotation, TAIR10, GeneRIFs], } @@ -105,3 +109,73 @@ def get(self, query=""): else: # return first 10 matches return {"status": "success", "query": query, "result": res[:10]} + + +anntn_post_ex = gene_annotation.model( + "AntnRiceGenes", + { + "species": fields.String(required=True, example="rice"), + "genes": fields.List( + required=True, + example=["LOC_Os01g01010", "LOC_Os01g01050"], + cls_or_instance=fields.String, + ), + }, +) + + +class GeneIntrnsSchema(Schema): + species = marshmallow_fields.String(required=True) + genes = marshmallow_fields.List(cls_or_instance=marshmallow_fields.String) + + +@gene_annotation.route("/") +class GeneAnnotationPost(Resource): + @gene_annotation.expect(anntn_post_ex) + def post(self, query=""): + """ + Returns gene annotation(s) give a set of gene(s) for a species + Supported species: 'rice' + """ + + json_data = request.get_json() + + try: + json_data = GeneIntrnsSchema().load(json_data) + except ValidationError as err: + return BARUtils.error_exit(err.messages), 400 + + species = json_data["species"].lower() + genes = json_data["genes"] + + if species == "rice": + for gene in genes: + if not BARUtils.is_rice_gene_valid(gene): + return BARUtils.error_exit("Invalid gene id"), 400 + + try: + rows = EplantRiceAnnotation.query.filter( + EplantRiceAnnotation.gene.in_(genes) + ).all() + if len(rows) == 0: + return ( + BARUtils.error_exit( + "No data for the given species/genes" + ), + 400, + ) + else: + print(rows) + res = [ + { + "gene": i.gene, + "annotation": i.annotation, + } + for i in rows + ] + return BARUtils.success_exit(res) + except OperationalError: + return BARUtils.error_exit("An internal error has occurred."), 500 + + else: + return BARUtils.error_exit("Invalid species"), 400 diff --git a/config/BAR_API.cfg b/config/BAR_API.cfg index 140ec1ac..5c452903 100644 --- a/config/BAR_API.cfg +++ b/config/BAR_API.cfg @@ -17,6 +17,7 @@ SQLALCHEMY_BINDS = { 'poplar_nssnp' : 'mysql://root:root@localhost/poplar_nssnp', 'tomato_nssnp' : 'mysql://root:root@localhost/tomato_nssnp', 'eplant_poplar' : 'mysql://root:root@localhost/eplant_poplar', + 'eplant_rice' : 'mysql://root:root@localhost/eplant_rice', 'eplant_tomato' : 'mysql://root:root@localhost/eplant_tomato', 'tomato_sequence' : 'mysql://root:root@localhost/tomato_sequence', 'rice_interactions': 'mysql://root:root@localhost/rice_interactions' diff --git a/config/databases/eplant_rice.sql b/config/databases/eplant_rice.sql new file mode 100644 index 00000000..959c988c --- /dev/null +++ b/config/databases/eplant_rice.sql @@ -0,0 +1,60 @@ +-- MySQL dump 10.13 Distrib 8.0.23, for Linux (x86_64) +-- +-- Host: localhost Database: eplant_rice +-- ------------------------------------------------------ +-- Server version 8.0.23-3 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Current Database: `eplant_rice` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `eplant_rice` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */; + +USE `eplant_rice`; + +-- +-- Table structure for table `gene_annotation` +-- + +DROP TABLE IF EXISTS `gene_annotation`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `gene_annotation` ( + `gene` varchar(20) NOT NULL, + `annotation` mediumtext NOT NULL, + PRIMARY KEY (`gene`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `gene_annotation` +-- +-- WHERE: 1 limit 5 + +LOCK TABLES `gene_annotation` WRITE; +/*!40000 ALTER TABLE `gene_annotation` DISABLE KEYS */; +INSERT INTO `gene_annotation` VALUES ('LOC_Os01g01010','protein TBC domain containing protein, expressed'),('LOC_Os01g01030','protein monocopper oxidase, putative, expressed'),('LOC_Os01g01050', 'protein R3H domain containing protein, expressed'); +/*!40000 ALTER TABLE `gene_annotation` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2021-06-26 19:08:20 diff --git a/config/init.sh b/config/init.sh index 82cfd24b..a8f1de8e 100755 --- a/config/init.sh +++ b/config/init.sh @@ -17,6 +17,7 @@ mysql -u $DB_USER -p$DB_PASS < ./config/databases/poplar_nssnp.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_nssnp.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_poplar.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_tomato.sql +mysql -u $DB_USER -p$DB_PASS < ./config/databases/eplant_rice.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/tomato_sequence.sql mysql -u $DB_USER -p$DB_PASS < ./config/databases/rice_interactions.sql diff --git a/tests/resources/test_gene_annotation.py b/tests/resources/test_gene_annotation.py index 11bd0a50..569421da 100644 --- a/tests/resources/test_gene_annotation.py +++ b/tests/resources/test_gene_annotation.py @@ -1,5 +1,6 @@ from api import app from unittest import TestCase +import json class TestIntegrations(TestCase): @@ -36,3 +37,58 @@ def test_keyword_search(self): "error": "There are no data found for the given query", } self.assertEqual(response.json, expected) + + def test_post_anntns(self): + """ + This function test retrieving gene annotations for various species' genes via POST. + """ + + # Valid request + response = self.app_client.post( + "/gene_annotation/", + json={"species": "rice", "genes": ["LOC_Os01g01010", "LOC_Os01g01050"]}, + ) + data = json.loads(response.get_data(as_text=True)) + expected = { + "wasSuccessful": True, + "data": [ + { + "gene": "LOC_Os01g01010", + "annotation": "protein TBC domain containing protein, expressed" + }, + { + "gene": "LOC_Os01g01050", + "annotation": "protein R3H domain containing protein, expressed" + } + ] + } + self.assertEqual(data, expected) + + # Invalid species + response = self.app_client.post( + "/interactions/", + json={"species": "poplar", "genes": ["LOC_Os01g01080", "LOC_Os01g73310"]}, + ) + data = json.loads(response.get_data(as_text=True)) + expected = {"wasSuccessful": False, "error": "Invalid species"} + self.assertEqual(data, expected) + + # Invalid gene ID + response = self.app_client.post( + "/interactions/", json={"species": "rice", "genes": ["abc", "xyz"]} + ) + data = json.loads(response.get_data(as_text=True)) + expected = {"wasSuccessful": False, "error": "Invalid gene id"} + self.assertEqual(data, expected) + + # No data for valid gene IDs + response = self.app_client.post( + "/interactions/", + json={"species": "rice", "genes": ["LOC_Os01g01085", "LOC_Os01g52565"]}, + ) + data = json.loads(response.get_data(as_text=True)) + expected = { + "wasSuccessful": False, + "error": "No data for the given species/genes", + } + self.assertEqual(data, expected)