3
3
import json
4
4
import gzip
5
5
import logging
6
+
7
+ from apispec import APISpec
8
+ from apispec .ext .marshmallow import MarshmallowPlugin
9
+ from apispec_webframeworks .flask import FlaskPlugin
10
+ from flask_apispec import FlaskApiSpec , doc , use_kwargs
11
+ from flask_swagger_ui import get_swaggerui_blueprint
6
12
from db import get_mongo_connection
7
13
from concurrent .futures import ThreadPoolExecutor
8
14
import configparser
9
- import urllib .parse
10
15
from typing import List , Dict , Optional , Any
11
16
from flask import Flask , jsonify , make_response , abort , render_template , request
12
17
from utils import map_gene
13
18
from gprofiler import GProfiler
19
+ from schemas import swagger_schemas
20
+
14
21
15
22
# Gets production flag
16
23
IS_DEBUG : bool = os .environ .get ('DEBUG' , 'true' ) == 'true'
@@ -515,9 +522,6 @@ def bfs_on_terms(term_id, relations: Optional[List[str]] = None, general_depth=0
515
522
return list (graph .values ())
516
523
517
524
518
- # PharmGKB
519
-
520
-
521
525
def cancer_drugs_related_to_gene (gene : str ) -> List :
522
526
"""
523
527
Gets all cancer related drugs associated with a gene .
@@ -527,6 +531,7 @@ def cancer_drugs_related_to_gene(gene: str) -> List:
527
531
collection_pharm = mydb ["pharmgkb" ]
528
532
return list (collection_pharm .find ({"genes" : gene }, {"_id" : 0 }))
529
533
534
+
530
535
def get_data_from_oncokb (genes : List [str ], query : str ) -> Dict [str , Dict [str , Any ]]:
531
536
"""
532
537
Gets all data from OncoKB database associated with a gene list.
@@ -643,33 +648,52 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) ->
643
648
return res
644
649
645
650
646
- # Documentation of included services
647
- services = [
648
- {"name" : "Genes symbols validator" , "url" : "[POST] /gene-symbols" },
649
- {"name" : "Genes symbols finder" , "url" : "[GET] /gene-symbols-finder" },
650
- {"name" : "Genes information" , "url" : "[POST] /information-of-genes" },
651
- {"name" : "Gene Groups" , "url" : "[GET] /genes-of-its-group/<gene_id>" },
652
- {"name" : "Genes of a metabolic pathway" , "url" : "[GET] /pathway-genes/<source>/<external_id>" },
653
- {"name" : "Metabolic pathways from different genes" , "url" : "[POST] /pathways-in-common" },
654
- {"name" : "Gene expression" , "url" : "[POST] /expression-of-genes" },
655
- {"name" : "Therapies and actionable genes in cancer" , "url" : "[POST] /information-of-oncokb" },
656
- {"name" : "Gene Ontology terms related to a list of genes" , "url" : "[POST] /genes-to-terms" },
657
- {"name" : "Gene Ontology terms related to another specific term" , "url" : "[POST] /related-terms" },
658
- {"name" : "Cancer related drugs" , "url" : "[POST] /drugs-pharm-gkb" },
659
- {"name" : "Predicted functional associations network" , "url" : "[POST] /string-relations" },
660
- {"name" : "Drugs that regulate a gene" , "url" : "[GET] /drugs-regulating-gene/<gene_id>" }
661
- ]
651
+ # Create an APISpec
652
+ spec = APISpec (
653
+ title = "BioAPI" ,
654
+ version = VERSION ,
655
+ openapi_version = "2.0.1" ,
656
+ info = dict (description = "A powerful abstraction of genomics databases." ),
657
+ plugins = [FlaskPlugin (), MarshmallowPlugin ()]
658
+ )
662
659
663
660
664
661
def create_app ():
665
662
# Creates and configures the app
666
663
flask_app = Flask (__name__ , instance_relative_config = True )
667
664
665
+ # URL for exposing Swagger UI
666
+ SWAGGER_URL = '/api/docs'
667
+ # Spec API url or path
668
+ API_URL = '/static/apispec.json'
669
+
670
+ # Call factory function to create our blueprint
671
+ swaggerui_blueprint = get_swaggerui_blueprint (SWAGGER_URL , API_URL , config = {
672
+ 'operationsSorter' : 'alpha' ,
673
+ 'tagsSorter' : 'alpha' ,
674
+ "defaultModelsExpandDepth" : - 1 , # Oculta la sección "Models"
675
+ 'filter' : True # Permite usar un campo de búsqueda para filtrar métodos en Swagger UI
676
+ })
677
+ flask_app .register_blueprint (swaggerui_blueprint )
678
+
679
+ flask_app .config .update ({'APISPEC_SPEC' : spec , 'APISPEC_SWAGGER_UI_URL' : SWAGGER_URL })
680
+
681
+ docs = FlaskApiSpec (flask_app )
682
+
668
683
# Endpoints
684
+ @flask_app .route (API_URL )
685
+ def swagger_json ():
686
+ """ Path to get OpenAPI Spec in ${API_URL}"""
687
+ schema = app .config ['APISPEC_SPEC' ].to_dict ()
688
+
689
+ for path , methods in schema .get ("paths" , {}).items ():
690
+ methods .pop ("options" , None )
691
+
692
+ return jsonify (schema )
693
+
669
694
@flask_app .route ("/" )
670
695
def homepage ():
671
- # return render_template('index.html', title=f"API v{VERSION}", services=services)
672
- return render_template ('homepage.html' , version = VERSION , services = services )
696
+ return render_template ('homepage.html' , version = VERSION )
673
697
674
698
@flask_app .route ("/ping" )
675
699
def ping_ok ():
@@ -678,16 +702,20 @@ def ping_ok():
678
702
return make_response (output , 200 , headers )
679
703
680
704
@flask_app .route ("/gene-symbols" , methods = ['POST' ])
681
- def gene_symbols ():
705
+ @doc (description = 'Gene symbols validator' , tags = ['Genes' ], consumes = ["application/json" ])
706
+ @use_kwargs (args = swagger_schemas .GeneSymbolsRequestSchema , location = "json" )
707
+ def gene_symbols (gene_ids ):
682
708
"""Receives a list of gene IDs in any standard and returns the standardized corresponding gene IDs.
683
709
In case it is not found it returns an empty list for the specific not found gene."""
684
710
response = {}
685
711
if request .method == 'POST' :
686
- body = request .get_json () # type: ignore
712
+ if not request .is_json :
713
+ abort (400 , "NO ES JSON!" )
714
+ body = request .get_json ()
687
715
if "gene_ids" not in body :
688
716
abort (400 , "gene_ids is mandatory" )
689
717
690
- gene_ids = body ['gene_ids' ]
718
+ # gene_ids = body['gene_ids']
691
719
if not isinstance (gene_ids , list ):
692
720
abort (400 , "gene_ids must be a list" )
693
721
@@ -700,7 +728,9 @@ def gene_symbols():
700
728
return make_response (response , 200 , headers )
701
729
702
730
@flask_app .route ("/gene-symbols-finder/" , methods = ['GET' ])
703
- def gene_symbol_finder ():
731
+ @doc (description = 'Gene symbols finder' , tags = ['Genes' ])
732
+ @use_kwargs (args = swagger_schemas .GeneSymbolsFinderRequestSchema , location = "query" )
733
+ def gene_symbol_finder (query : str , limit : int | None ):
704
734
"""Takes a string of any length and returns a list of genes that contain that search criteria."""
705
735
if "query" not in request .args :
706
736
abort (400 , "'query' parameter is mandatory" )
@@ -1045,6 +1075,10 @@ def bad_request(e):
1045
1075
def not_found (e ):
1046
1076
return jsonify (error = str (e )), 404
1047
1077
1078
+ with flask_app .test_request_context ():
1079
+ docs .register (target = gene_symbols )
1080
+ docs .register (target = gene_symbol_finder )
1081
+
1048
1082
return flask_app
1049
1083
1050
1084
0 commit comments