3
3
import json
4
4
import gzip
5
5
import logging
6
- from turtle import title
7
6
8
- from apispec import APISpec
9
- from apispec .ext .marshmallow import MarshmallowPlugin
10
- from apispec_webframeworks .flask import FlaskPlugin
11
- from flask_apispec import FlaskApiSpec , doc , use_kwargs
12
- from flask_swagger_ui import get_swaggerui_blueprint
7
+ from flask import Flask , jsonify , make_response , abort , render_template , request
8
+ # from apispec import APISpec
9
+ # from apispec.ext.marshmallow import MarshmallowPlugin
10
+ # from apispec_webframeworks.flask import FlaskPlugin
11
+ # from flask_swagger_ui import get_swaggerui_blueprint
12
+ from flasgger import Swagger , swag_from
13
13
from db import get_mongo_connection
14
14
from concurrent .futures import ThreadPoolExecutor
15
15
import configparser
16
16
from typing import List , Dict , Optional , Any
17
- from flask import Flask , jsonify , make_response , abort , render_template , request
18
17
from utils import map_gene
19
18
from gprofiler import GProfiler
20
- from schemas import swagger_schemas
21
19
22
20
23
21
# Gets production flag
@@ -650,55 +648,50 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) ->
650
648
651
649
652
650
# Create an APISpec
653
- spec = APISpec (
654
- title = "BioAPI" ,
655
- version = VERSION ,
656
- openapi_version = "2.0.0" ,
657
- info = dict (
658
- description = """
659
- ## A powerful abstraction of genomics databases.
660
- BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/).
661
- To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)""" ),
662
- plugins = [FlaskPlugin (), MarshmallowPlugin ()]
663
- )
651
+ # spec = APISpec(
652
+ # title="BioAPI",
653
+ # version=VERSION,
654
+ # openapi_version="2.0.0",
655
+ # info=dict(
656
+ # description="""
657
+ # # # A powerful abstraction of genomics databases.
658
+ # BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/).
659
+ # To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""),
660
+ # plugins=[FlaskPlugin()],
661
+ # )
664
662
665
663
666
664
def create_app ():
667
665
# Creates and configures the app
668
666
flask_app = Flask (__name__ , instance_relative_config = True )
669
-
670
- # URL for exposing Swagger UI
671
- SWAGGER_URL = '/api/docs'
672
- # Spec API url or path
673
- API_URL = '/static/apispec.json'
674
-
675
- # Config and Call factory function to create our blueprint
676
- swagger_ui_config = {
677
- 'docExpansion' : False , # Avoid extended tags
678
- 'displayRequestDuration' : False , # Hide the duration of the request
679
- 'tryItOutEnabled' : False , # Enables testing functions without clicking "Try it out"
680
- 'supportedSubmitMethods' : ['get' , 'post' ], # Allows testing only GET and POST methods
681
- 'validatorUrl' : False , # Avoid online validation of documentation
682
- "defaultModelsExpandDepth" : - 1 , # Hide the Models section
683
- 'filter' : False , # Hide the method filter field
667
+ swagger_config = {
668
+ "headers" : [
669
+ ],
670
+ "openapi" : "3.0.0" ,
671
+ "specs" : [
672
+ {
673
+ "endpoint" : "swagger" ,
674
+ "route" : "/apispec.json" ,
675
+ "rule_filter" : lambda rule : True , # all in
676
+ "model_filter" : lambda tag : True , # all in
677
+ }
678
+ ],
679
+ "title" : "BioAPI" ,
680
+ "uiversion" : 3 ,
681
+ "version" : VERSION ,
682
+ "termsOfService" : False ,
683
+ "swagger_ui" : True ,
684
+ "static_url_path" : "/" ,
685
+ "specs_route" : "/apidocs/" ,
686
+ "description" : """
687
+ ## A powerful abstraction of genomics databases.
688
+ BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/).
689
+ To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""
684
690
}
685
- swaggerui_blueprint = get_swaggerui_blueprint (SWAGGER_URL , API_URL , config = swagger_ui_config )
686
- flask_app .register_blueprint (swaggerui_blueprint )
687
691
688
- flask_app .config .update ({'APISPEC_SPEC' : spec , 'APISPEC_SWAGGER_UI_URL' : SWAGGER_URL })
689
-
690
- docs = FlaskApiSpec (flask_app )
692
+ swagger = Swagger (flask_app , config = swagger_config )
691
693
692
694
# Endpoints
693
- @flask_app .route (API_URL )
694
- def swagger_json ():
695
- """ Path to get OpenAPI Spec in ${API_URL}"""
696
- schema = app .config ['APISPEC_SPEC' ].to_dict ()
697
- for path , methods in schema .get ("paths" , {}).items ():
698
- methods .pop ("options" , None )
699
-
700
- return jsonify (schema )
701
-
702
695
@flask_app .route ("/" )
703
696
def homepage ():
704
697
return render_template ('homepage.html' , version = VERSION )
@@ -710,20 +703,17 @@ def ping_ok():
710
703
return make_response (output , 200 , headers )
711
704
712
705
@flask_app .route ("/gene-symbols" , methods = ['POST' ])
713
- @doc (description = 'Gene symbols validator' , tags = ['Genes' ], consumes = ["application/json" ])
714
- @use_kwargs (args = swagger_schemas .GeneSymbolsRequestSchema , location = "json" )
715
- def gene_symbols (gene_ids ):
706
+ @swag_from ("swagger_specs/geneSymbols.yml" )
707
+ def gene_symbols ():
716
708
"""Receives a list of gene IDs in any standard and returns the standardized corresponding gene IDs.
717
709
In case it is not found it returns an empty list for the specific not found gene."""
718
710
response = {}
719
711
if request .method == 'POST' :
720
- if not request .is_json :
721
- abort (400 , "NO ES JSON!" )
722
712
body = request .get_json ()
723
713
if "gene_ids" not in body :
724
714
abort (400 , "gene_ids is mandatory" )
725
715
726
- # gene_ids = body['gene_ids']
716
+ gene_ids = body ['gene_ids' ]
727
717
if not isinstance (gene_ids , list ):
728
718
abort (400 , "gene_ids must be a list" )
729
719
@@ -736,18 +726,19 @@ def gene_symbols(gene_ids):
736
726
return make_response (response , 200 , headers )
737
727
738
728
@flask_app .route ("/gene-symbols-finder/" , methods = ['GET' ])
739
- @doc (description = 'Gene symbols finder' , tags = ['Genes' ])
740
- @use_kwargs (args = swagger_schemas .GeneSymbolsFinderRequestSchema , location = "query" )
741
- def gene_symbol_finder (query : str , limit : int | None ):
729
+ # @doc(description='Gene symbols finder', tags=['Genes'])
730
+ # @use_kwargs(args=swagger_schemas.GeneSymbolsFinderRequestSchema, location="query")
731
+ @swag_from ("swagger_specs/geneSymbolFinder.yml" )
732
+ def gene_symbol_finder ():
742
733
"""Takes a string of any length and returns a list of genes that contain that search criteria."""
743
734
if "query" not in request .args :
744
735
abort (400 , "'query' parameter is mandatory" )
745
736
else :
746
- query = request .args .get ('query' ) # type: ignore
737
+ query = request .args .get ('query' )
747
738
748
739
limit = 50
749
740
if "limit" in request .args :
750
- limit_arg = request .args .get ('limit' ) # type: ignore
741
+ limit_arg = request .args .get ('limit' )
751
742
if limit_arg .isnumeric ():
752
743
limit = int (limit_arg )
753
744
else :
@@ -760,6 +751,9 @@ def gene_symbol_finder(query: str, limit: int|None):
760
751
abort (400 , e )
761
752
762
753
@flask_app .route ("/information-of-genes" , methods = ['POST' ])
754
+ # @doc(description='Genes information', tags=['Genes'], consumes=["application/json"])
755
+ # @use_kwargs(args=swagger_schemas.InformationOfGenesRequestSchema, location="json")
756
+ @swag_from ("swagger_specs/informationOfGenes.yml" )
763
757
def information_of_genes ():
764
758
"""Receives a list of gene IDs and returns information about them."""
765
759
body = request .get_json () # type: ignore
@@ -777,7 +771,9 @@ def information_of_genes():
777
771
return make_response (response , 200 , headers )
778
772
779
773
@flask_app .route ("/genes-of-its-group/<gene_id>" , methods = ['GET' ])
780
- def genes_in_the_same_group (gene_id ):
774
+ # @doc(description='Gene Groups', tags=['Genes'], params={"gene_id": {"description": "Identifier of the gene for any database", "type": "string", "required": True}})
775
+ @swag_from ("swagger_specs/genesOfItsGroup.yml" )
776
+ def genes_in_the_same_group (gene_id : str ):
781
777
response = {"gene_id" : None , "groups" : [],
782
778
"locus_group" : None , "locus_type" : None }
783
779
try :
@@ -812,6 +808,10 @@ def genes_in_the_same_group(gene_id):
812
808
return make_response (response , 200 , headers )
813
809
814
810
@flask_app .route ("/pathway-genes/<pathway_source>/<pathway_id>" , methods = ['GET' ])
811
+ @swag_from ("swagger_specs/genesOfMetabolicPathway.yml" )
812
+ # @doc(description='Genes of a metabolic pathway', tags=['Genes'],
813
+ # params={"pathway_source": {"description": "Database to query", "type": "string", "required": True, "example": "kegg", "enum": ["kegg", "biocarta", "ehmn", "humancyc", "inoh", "netpath", "pid", "reactome", "smpdb", "signalink", "wikipathways"]},
814
+ # "pathway_id": {"description": "Pathway identifier in the source database", "type": "string", "required": True, "example": "hsa00740"}})
815
815
def pathway_genes (pathway_source , pathway_id ):
816
816
if pathway_source .lower () not in PATHWAYS_SOURCES :
817
817
abort (404 , f'{ pathway_source } is an invalid pathway source' )
@@ -820,16 +820,18 @@ def pathway_genes(pathway_source, pathway_id):
820
820
return make_response (response , 200 , headers )
821
821
822
822
@flask_app .route ("/pathways-in-common" , methods = ['POST' ])
823
- def pathways_in_common ():
824
- body = request .get_json () # type: ignore
825
- if "gene_ids" not in body :
826
- abort (400 , "gene_ids is mandatory" )
827
-
828
- gene_ids = body ['gene_ids' ]
829
- if not isinstance (gene_ids , list ):
830
- abort (400 , "gene_ids must be a list" )
831
- if len (gene_ids ) == 0 :
832
- abort (400 , "gene_ids must contain at least one gene symbol" )
823
+ # @doc(description='Metabolic pathways from different genes', tags=['Pathways'], consumes=["application/json"])
824
+ # @use_kwargs(args=swagger_schemas.PathwaysInCommonRequestSchema, location="json")
825
+ def pathways_in_common (gene_ids : List [str ]):
826
+ # body = request.get_json() # type: ignore
827
+ # if "gene_ids" not in body:
828
+ # abort(400, "gene_ids is mandatory")
829
+
830
+ # gene_ids = body['gene_ids']
831
+ # if not isinstance(gene_ids, list):
832
+ # abort(400, "gene_ids must be a list")
833
+ # if len(gene_ids) == 0:
834
+ # abort(400, "gene_ids must contain at least one gene symbol")
833
835
834
836
pathways_tmp = [get_pathways_of_gene (gene ) for gene in gene_ids ]
835
837
pathways_intersection = list (set .intersection (* map (set , pathways_tmp )))
@@ -840,34 +842,36 @@ def pathways_in_common():
840
842
return make_response (response , 200 , headers )
841
843
842
844
@flask_app .route ("/expression-of-genes" , methods = ['POST' ])
843
- def expression_data_from_gtex ():
844
- body = request .get_json () # type: ignore
845
+ # @doc(description='Gets gene expression in healthy tissue', tags=['Expression Data'], consumes=["application/json"])
846
+ # @use_kwargs(args=swagger_schemas.ExpressionOfGenesRequestSchema, location="json")
847
+ def expression_data_from_gtex (gene_ids : list [str ], tissue : str , type : str ):
848
+ # body = request.get_json() # type: ignore
845
849
846
- if "gene_ids" not in body :
847
- abort (400 , "gene_ids is mandatory" )
850
+ # if "gene_ids" not in body:
851
+ # abort(400, "gene_ids is mandatory")
848
852
849
- gene_ids = body ['gene_ids' ]
850
- if not isinstance (gene_ids , list ):
851
- abort (400 , "gene_ids must be a list" )
853
+ # gene_ids = body['gene_ids']
854
+ # if not isinstance(gene_ids, list):
855
+ # abort(400, "gene_ids must be a list")
852
856
853
- if len (gene_ids ) == 0 :
854
- abort (400 , "gene_ids must contain at least one gene symbol" )
857
+ # if len(gene_ids) == 0:
858
+ # abort(400, "gene_ids must contain at least one gene symbol")
855
859
856
- if "tissue" not in body :
857
- abort (400 , "tissue is mandatory" )
860
+ # if "tissue" not in body:
861
+ # abort(400, "tissue is mandatory")
858
862
859
- tissue = body ['tissue' ]
860
- if "type" in body :
861
- if body ['type' ] not in ["gzip" , "json" ]:
862
- abort (400 , "allowed values for the 'type' key are 'json' or 'gzip'" )
863
- else :
864
- type_response = body ['type' ]
865
- else :
866
- type_response = 'json'
863
+ # tissue = body['tissue']
864
+ # if "type" in body:
865
+ # if body['type'] not in ["gzip", "json"]:
866
+ # abort(400, "allowed values for the 'type' key are 'json' or 'gzip'")
867
+ # else:
868
+ # type_response = body['type']
869
+ # else:
870
+ # type_response = 'json'
867
871
868
872
expression_data = get_expression_from_gtex (tissue , gene_ids )
869
873
870
- if type_response == "gzip" :
874
+ if type == "gzip" :
871
875
content = gzip .compress (json .dumps (
872
876
expression_data ).encode ('utf8' ), 5 )
873
877
response = make_response (content )
@@ -877,7 +881,9 @@ def expression_data_from_gtex():
877
881
return jsonify (expression_data )
878
882
879
883
@flask_app .route ("/genes-to-terms" , methods = ['POST' ])
880
- def genes_to_go_terms ():
884
+ # @doc(description='Gene Ontology terms related to a list of genes', tags=['Gene Ontology'], consumes=["application/json"])
885
+ # @use_kwargs(args=swagger_schemas.GenesToTermsRequestSchema, location="json")
886
+ def genes_to_go_terms (gene_ids : list [str ], filter_type : str , p_value_threshold : float | None , correction_method : str , relation_type : list [str ], ontology_type : list [str ]):
881
887
"""Receives a list of genes and returns the related terms"""
882
888
valid_filter_types = ["union" , "intersection" , "enrichment" ]
883
889
valid_ontology_types = ["biological_process" ,
@@ -1011,17 +1017,19 @@ def related_terms():
1011
1017
return jsonify (response )
1012
1018
1013
1019
@flask_app .route ("/information-of-oncokb" , methods = ['POST' ])
1014
- def oncokb_data ():
1015
- body = request .get_json () # type: ignore
1020
+ # @doc(description='Therapies and actionable genes in cancer', tags=['Genes'], consumes=["application/json"])
1021
+ # @use_kwargs(args=swagger_schemas.InformationOfOncokbRequestSchema, location="json")
1022
+ def oncokb_data (gene_ids : list [str ], query : str ):
1023
+ # body = request.get_json() # type: ignore
1016
1024
1017
- if "gene_ids" not in body :
1018
- abort (400 , "gene_ids is mandatory" )
1025
+ # if "gene_ids" not in body:
1026
+ # abort(400, "gene_ids is mandatory")
1019
1027
1020
- gene_ids = body ['gene_ids' ]
1021
- query = "" if "query" not in body else body ['query' ]
1028
+ # gene_ids = body['gene_ids']
1029
+ # query = "" if "query" not in body else body['query']
1022
1030
1023
- if not isinstance (gene_ids , list ):
1024
- abort (400 , "gene_ids must be a list" )
1031
+ # if not isinstance(gene_ids, list):
1032
+ # abort(400, "gene_ids must be a list")
1025
1033
1026
1034
if len (gene_ids ) == 0 :
1027
1035
abort (400 , "gene_ids must contain at least one gene symbol" )
@@ -1083,10 +1091,6 @@ def bad_request(e):
1083
1091
def not_found (e ):
1084
1092
return jsonify (error = str (e )), 404
1085
1093
1086
- with flask_app .test_request_context ():
1087
- docs .register (target = gene_symbols )
1088
- docs .register (target = gene_symbol_finder )
1089
-
1090
1094
return flask_app
1091
1095
1092
1096
0 commit comments