Skip to content

Commit a0592e8

Browse files
committed
replace flask_apispec for flasgger
1 parent be8624e commit a0592e8

10 files changed

+303
-125
lines changed

bio-api/bioapi.py

+107-103
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,19 @@
33
import json
44
import gzip
55
import logging
6-
from turtle import title
76

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
1313
from db import get_mongo_connection
1414
from concurrent.futures import ThreadPoolExecutor
1515
import configparser
1616
from typing import List, Dict, Optional, Any
17-
from flask import Flask, jsonify, make_response, abort, render_template, request
1817
from utils import map_gene
1918
from gprofiler import GProfiler
20-
from schemas import swagger_schemas
2119

2220

2321
# Gets production flag
@@ -650,55 +648,50 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) ->
650648

651649

652650
# 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+
# )
664662

665663

666664
def create_app():
667665
# Creates and configures the app
668666
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)"""
684690
}
685-
swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL, config=swagger_ui_config)
686-
flask_app.register_blueprint(swaggerui_blueprint)
687691

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)
691693

692694
# 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-
702695
@flask_app.route("/")
703696
def homepage():
704697
return render_template('homepage.html', version=VERSION)
@@ -710,20 +703,17 @@ def ping_ok():
710703
return make_response(output, 200, headers)
711704

712705
@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():
716708
"""Receives a list of gene IDs in any standard and returns the standardized corresponding gene IDs.
717709
In case it is not found it returns an empty list for the specific not found gene."""
718710
response = {}
719711
if request.method == 'POST':
720-
if not request.is_json:
721-
abort(400, "NO ES JSON!")
722712
body = request.get_json()
723713
if "gene_ids" not in body:
724714
abort(400, "gene_ids is mandatory")
725715

726-
# gene_ids = body['gene_ids']
716+
gene_ids = body['gene_ids']
727717
if not isinstance(gene_ids, list):
728718
abort(400, "gene_ids must be a list")
729719

@@ -736,18 +726,19 @@ def gene_symbols(gene_ids):
736726
return make_response(response, 200, headers)
737727

738728
@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():
742733
"""Takes a string of any length and returns a list of genes that contain that search criteria."""
743734
if "query" not in request.args:
744735
abort(400, "'query' parameter is mandatory")
745736
else:
746-
query = request.args.get('query') # type: ignore
737+
query = request.args.get('query')
747738

748739
limit = 50
749740
if "limit" in request.args:
750-
limit_arg = request.args.get('limit') # type: ignore
741+
limit_arg = request.args.get('limit')
751742
if limit_arg.isnumeric():
752743
limit = int(limit_arg)
753744
else:
@@ -760,6 +751,9 @@ def gene_symbol_finder(query: str, limit: int|None):
760751
abort(400, e)
761752

762753
@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")
763757
def information_of_genes():
764758
"""Receives a list of gene IDs and returns information about them."""
765759
body = request.get_json() # type: ignore
@@ -777,7 +771,9 @@ def information_of_genes():
777771
return make_response(response, 200, headers)
778772

779773
@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):
781777
response = {"gene_id": None, "groups": [],
782778
"locus_group": None, "locus_type": None}
783779
try:
@@ -812,6 +808,10 @@ def genes_in_the_same_group(gene_id):
812808
return make_response(response, 200, headers)
813809

814810
@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"}})
815815
def pathway_genes(pathway_source, pathway_id):
816816
if pathway_source.lower() not in PATHWAYS_SOURCES:
817817
abort(404, f'{pathway_source} is an invalid pathway source')
@@ -820,16 +820,18 @@ def pathway_genes(pathway_source, pathway_id):
820820
return make_response(response, 200, headers)
821821

822822
@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")
833835

834836
pathways_tmp = [get_pathways_of_gene(gene) for gene in gene_ids]
835837
pathways_intersection = list(set.intersection(*map(set, pathways_tmp)))
@@ -840,34 +842,36 @@ def pathways_in_common():
840842
return make_response(response, 200, headers)
841843

842844
@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
845849

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")
848852

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")
852856

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")
855859

856-
if "tissue" not in body:
857-
abort(400, "tissue is mandatory")
860+
# if "tissue" not in body:
861+
# abort(400, "tissue is mandatory")
858862

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'
867871

868872
expression_data = get_expression_from_gtex(tissue, gene_ids)
869873

870-
if type_response == "gzip":
874+
if type == "gzip":
871875
content = gzip.compress(json.dumps(
872876
expression_data).encode('utf8'), 5)
873877
response = make_response(content)
@@ -877,7 +881,9 @@ def expression_data_from_gtex():
877881
return jsonify(expression_data)
878882

879883
@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]):
881887
"""Receives a list of genes and returns the related terms"""
882888
valid_filter_types = ["union", "intersection", "enrichment"]
883889
valid_ontology_types = ["biological_process",
@@ -1011,17 +1017,19 @@ def related_terms():
10111017
return jsonify(response)
10121018

10131019
@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
10161024

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")
10191027

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']
10221030

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")
10251033

10261034
if len(gene_ids) == 0:
10271035
abort(400, "gene_ids must contain at least one gene symbol")
@@ -1083,10 +1091,6 @@ def bad_request(e):
10831091
def not_found(e):
10841092
return jsonify(error=str(e)), 404
10851093

1086-
with flask_app.test_request_context():
1087-
docs.register(target=gene_symbols)
1088-
docs.register(target=gene_symbol_finder)
1089-
10901094
return flask_app
10911095

10921096

bio-api/schemas/swagger_schemas.py

-17
This file was deleted.
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tags:
2+
- Gene Nomenclature
3+
summary: "Gene symbol finder"
4+
description: "Service that takes a string of any length and returns a list of genes that contain that search criteria."
5+
operationId: "Gene symbol finder"
6+
parameters:
7+
- in: query
8+
name: query
9+
description: "Gene search string."
10+
required: true
11+
schema:
12+
type: string
13+
example: "TP"
14+
- in: query
15+
name: limit
16+
description: "Limit the number of results returned (Default 50)."
17+
required: false
18+
schema:
19+
type: integer
20+
example: 10
21+
responses:
22+
200:
23+
description: "List of genes containing that search criterion in bioinformatics databases."

bio-api/swagger_specs/geneSymbols.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
tags:
2+
- Gene Nomenclature
3+
summary: "Gene symbol validator"
4+
description: "Searches the identifier of a list of genes of different genomics databases and returns the approved symbols according to HGNC nomenclature."
5+
operationId: "Gene symbol"
6+
requestBody:
7+
required: true
8+
content:
9+
application/json:
10+
schema:
11+
type: object
12+
properties:
13+
gene_ids:
14+
type: array
15+
description: "List of gene identifiers."
16+
items:
17+
type: string
18+
example: ["FANCS", "BRCC1"]
19+
responses:
20+
200:
21+
description: "HGNC approved gene symbols."

0 commit comments

Comments
 (0)