From 4a67afcbedb7862d9c1d8fc47adba795d779b2ac Mon Sep 17 00:00:00 2001 From: mauricio Date: Thu, 14 Nov 2024 18:17:53 -0300 Subject: [PATCH 1/5] Add Filter and Remove OPTION methods and Models Section from Swagger UI --- bio-api/bioapi.py | 86 ++++++++++++++++++++--------- bio-api/schemas/swagger_schemas.py | 17 ++++++ bio-api/templates/homepage.html | 53 +++--------------- config/bioapi_conf/requirements.txt | 10 +++- 4 files changed, 94 insertions(+), 72 deletions(-) create mode 100644 bio-api/schemas/swagger_schemas.py diff --git a/bio-api/bioapi.py b/bio-api/bioapi.py index abba7e8..06e774b 100755 --- a/bio-api/bioapi.py +++ b/bio-api/bioapi.py @@ -3,14 +3,21 @@ import json import gzip import logging + +from apispec import APISpec +from apispec.ext.marshmallow import MarshmallowPlugin +from apispec_webframeworks.flask import FlaskPlugin +from flask_apispec import FlaskApiSpec, doc, use_kwargs +from flask_swagger_ui import get_swaggerui_blueprint from db import get_mongo_connection from concurrent.futures import ThreadPoolExecutor import configparser -import urllib.parse from typing import List, Dict, Optional, Any from flask import Flask, jsonify, make_response, abort, render_template, request from utils import map_gene from gprofiler import GProfiler +from schemas import swagger_schemas + # Gets production flag 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 return list(graph.values()) -# PharmGKB - - def cancer_drugs_related_to_gene(gene: str) -> List: """ Gets all cancer related drugs associated with a gene . @@ -527,6 +531,7 @@ def cancer_drugs_related_to_gene(gene: str) -> List: collection_pharm = mydb["pharmgkb"] return list(collection_pharm.find({"genes": gene}, {"_id": 0})) + def get_data_from_oncokb(genes: List[str], query: str) -> Dict[str, Dict[str, Any]]: """ 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) -> return res -# Documentation of included services -services = [ - {"name": "Genes symbols validator", "url": "[POST] /gene-symbols"}, - {"name": "Genes symbols finder", "url": "[GET] /gene-symbols-finder"}, - {"name": "Genes information", "url": "[POST] /information-of-genes"}, - {"name": "Gene Groups", "url": "[GET] /genes-of-its-group/"}, - {"name": "Genes of a metabolic pathway", "url": "[GET] /pathway-genes//"}, - {"name": "Metabolic pathways from different genes", "url": "[POST] /pathways-in-common"}, - {"name": "Gene expression", "url": "[POST] /expression-of-genes"}, - {"name": "Therapies and actionable genes in cancer", "url": "[POST] /information-of-oncokb"}, - {"name": "Gene Ontology terms related to a list of genes", "url": "[POST] /genes-to-terms"}, - {"name": "Gene Ontology terms related to another specific term", "url": "[POST] /related-terms"}, - {"name": "Cancer related drugs", "url": "[POST] /drugs-pharm-gkb"}, - {"name": "Predicted functional associations network", "url": "[POST] /string-relations"}, - {"name": "Drugs that regulate a gene", "url": "[GET] /drugs-regulating-gene/"} -] +# Create an APISpec +spec = APISpec( + title="BioAPI", + version=VERSION, + openapi_version="2.0.1", + info=dict(description="A powerful abstraction of genomics databases."), + plugins=[FlaskPlugin(), MarshmallowPlugin()] +) def create_app(): # Creates and configures the app flask_app = Flask(__name__, instance_relative_config=True) + # URL for exposing Swagger UI + SWAGGER_URL = '/api/docs' + # Spec API url or path + API_URL = '/static/apispec.json' + + # Call factory function to create our blueprint + swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL, config={ + 'operationsSorter': 'alpha', + 'tagsSorter': 'alpha', + "defaultModelsExpandDepth": -1, # Oculta la sección "Models" + 'filter': True # Permite usar un campo de búsqueda para filtrar métodos en Swagger UI + }) + flask_app.register_blueprint(swaggerui_blueprint) + + flask_app.config.update({'APISPEC_SPEC': spec, 'APISPEC_SWAGGER_UI_URL': SWAGGER_URL}) + + docs = FlaskApiSpec(flask_app) + # Endpoints + @flask_app.route(API_URL) + def swagger_json(): + """ Path to get OpenAPI Spec in ${API_URL}""" + schema = app.config['APISPEC_SPEC'].to_dict() + + for path, methods in schema.get("paths", {}).items(): + methods.pop("options", None) + + return jsonify(schema) + @flask_app.route("/") def homepage(): - # return render_template('index.html', title=f"API v{VERSION}", services=services) - return render_template('homepage.html', version=VERSION, services=services) + return render_template('homepage.html', version=VERSION) @flask_app.route("/ping") def ping_ok(): @@ -678,16 +702,20 @@ def ping_ok(): return make_response(output, 200, headers) @flask_app.route("/gene-symbols", methods=['POST']) - def gene_symbols(): + @doc(description='Gene symbols validator', tags=['Genes'], consumes=["application/json"]) + @use_kwargs(args=swagger_schemas.GeneSymbolsRequestSchema, location="json") + def gene_symbols(gene_ids): """Receives a list of gene IDs in any standard and returns the standardized corresponding gene IDs. In case it is not found it returns an empty list for the specific not found gene.""" response = {} if request.method == 'POST': - body = request.get_json() # type: ignore + if not request.is_json: + abort(400, "NO ES JSON!") + body = request.get_json() if "gene_ids" not in body: abort(400, "gene_ids is mandatory") - gene_ids = body['gene_ids'] + # gene_ids = body['gene_ids'] if not isinstance(gene_ids, list): abort(400, "gene_ids must be a list") @@ -700,7 +728,9 @@ def gene_symbols(): return make_response(response, 200, headers) @flask_app.route("/gene-symbols-finder/", methods=['GET']) - def gene_symbol_finder(): + @doc(description='Gene symbols finder', tags=['Genes']) + @use_kwargs(args=swagger_schemas.GeneSymbolsFinderRequestSchema, location="query") + def gene_symbol_finder(query: str, limit: int|None): """Takes a string of any length and returns a list of genes that contain that search criteria.""" if "query" not in request.args: abort(400, "'query' parameter is mandatory") @@ -1045,6 +1075,10 @@ def bad_request(e): def not_found(e): return jsonify(error=str(e)), 404 + with flask_app.test_request_context(): + docs.register(target=gene_symbols) + docs.register(target=gene_symbol_finder) + return flask_app diff --git a/bio-api/schemas/swagger_schemas.py b/bio-api/schemas/swagger_schemas.py new file mode 100644 index 0000000..8563fed --- /dev/null +++ b/bio-api/schemas/swagger_schemas.py @@ -0,0 +1,17 @@ +from marshmallow import Schema, fields + + +class GeneSymbolsRequestSchema(Schema): + gene_ids = fields.List(fields.String(), required=True, example=["FANCS", "BRCC1"]) + + +class GeneSymbolsFinderRequestSchema(Schema): + query = fields.String( + required=True, + description="gene search string", + example="TP" + ) + limit = fields.Integer( + missing=50, + description="number of elements returned by the service. Default 50." + ) diff --git a/bio-api/templates/homepage.html b/bio-api/templates/homepage.html index e1fb0ac..4b7e96e 100644 --- a/bio-api/templates/homepage.html +++ b/bio-api/templates/homepage.html @@ -1,54 +1,19 @@ - - + - - - BioAPI v{{ version }} - + + BioAPI {{version}} + -

BioAPI v{{ version }}

-

Services included:

-
    - {% for service in services %} -
  • - {{ service.name }} - {{ service.url }} -
  • - {% endfor %} -
- +

BioAPI {{ version }}

+

A powerful abstraction of genomics databases

+

Documentation

\ No newline at end of file diff --git a/config/bioapi_conf/requirements.txt b/config/bioapi_conf/requirements.txt index 3e33ad2..666d9ee 100755 --- a/config/bioapi_conf/requirements.txt +++ b/config/bioapi_conf/requirements.txt @@ -1,9 +1,15 @@ pymongo==4.6.1 Flask==3.0.0 -flask-cors==4.0.0 +flask-cors==5.0.0 gunicorn==21.2.0 ConfigParser==6.0.0 pytest==7.1.2 tqdm==4.66.1 gprofiler-official==1.0.0 -Werkzeug==3.0.1 \ No newline at end of file +Werkzeug==3.0.1 +flask_swagger_ui==4.11.1 +apispec==6.7.1 +flask-apispec==0.11.4 +apispec-webframeworks==1.2.0 +marshmallow==3.23.1 +setuptools==75.5.0 \ No newline at end of file From be8624e81f556f68b70408982becc2ef18364afd Mon Sep 17 00:00:00 2001 From: mauricio Date: Sat, 16 Nov 2024 21:05:25 -0300 Subject: [PATCH 2/5] swagger interface customization --- bio-api/bioapi.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/bio-api/bioapi.py b/bio-api/bioapi.py index 06e774b..8ad6c1d 100755 --- a/bio-api/bioapi.py +++ b/bio-api/bioapi.py @@ -3,6 +3,7 @@ import json import gzip import logging +from turtle import title from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin @@ -652,8 +653,12 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) -> spec = APISpec( title="BioAPI", version=VERSION, - openapi_version="2.0.1", - info=dict(description="A powerful abstraction of genomics databases."), + openapi_version="2.0.0", + info=dict( + description=""" +## A powerful abstraction of genomics databases. +BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/). +To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""), plugins=[FlaskPlugin(), MarshmallowPlugin()] ) @@ -667,13 +672,17 @@ def create_app(): # Spec API url or path API_URL = '/static/apispec.json' - # Call factory function to create our blueprint - swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL, config={ - 'operationsSorter': 'alpha', - 'tagsSorter': 'alpha', - "defaultModelsExpandDepth": -1, # Oculta la sección "Models" - 'filter': True # Permite usar un campo de búsqueda para filtrar métodos en Swagger UI - }) + # Config and Call factory function to create our blueprint + swagger_ui_config = { + 'docExpansion': False, # Avoid extended tags + 'displayRequestDuration': False, # Hide the duration of the request + 'tryItOutEnabled': False, # Enables testing functions without clicking "Try it out" + 'supportedSubmitMethods': ['get', 'post'], # Allows testing only GET and POST methods + 'validatorUrl': False, # Avoid online validation of documentation + "defaultModelsExpandDepth": -1, # Hide the Models section + 'filter': False, # Hide the method filter field + } + swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL, config=swagger_ui_config) flask_app.register_blueprint(swaggerui_blueprint) flask_app.config.update({'APISPEC_SPEC': spec, 'APISPEC_SWAGGER_UI_URL': SWAGGER_URL}) @@ -684,8 +693,7 @@ def create_app(): @flask_app.route(API_URL) def swagger_json(): """ Path to get OpenAPI Spec in ${API_URL}""" - schema = app.config['APISPEC_SPEC'].to_dict() - + schema = app.config['APISPEC_SPEC'].to_dict() for path, methods in schema.get("paths", {}).items(): methods.pop("options", None) From a0592e80e61abe3f256fc1c33fa0da8d3533d3ef Mon Sep 17 00:00:00 2001 From: mauricio Date: Sun, 24 Nov 2024 19:25:52 -0300 Subject: [PATCH 3/5] replace flask_apispec for flasgger --- bio-api/bioapi.py | 210 +++++++++--------- bio-api/schemas/swagger_schemas.py | 17 -- bio-api/swagger_specs/geneSymbolFinder.yml | 23 ++ bio-api/swagger_specs/geneSymbols.yml | 21 ++ bio-api/swagger_specs/genesOfItsGroup.yml | 16 ++ .../swagger_specs/genesOfMetabolicPathway.yml | 35 +++ bio-api/swagger_specs/informationOfGenes.yml | 21 ++ bio-api/swagger_specs/swagger_schemas.py | 75 +++++++ bio-api/templates/homepage.html | 2 +- config/bioapi_conf/requirements.txt | 8 +- 10 files changed, 303 insertions(+), 125 deletions(-) delete mode 100644 bio-api/schemas/swagger_schemas.py create mode 100644 bio-api/swagger_specs/geneSymbolFinder.yml create mode 100644 bio-api/swagger_specs/geneSymbols.yml create mode 100644 bio-api/swagger_specs/genesOfItsGroup.yml create mode 100644 bio-api/swagger_specs/genesOfMetabolicPathway.yml create mode 100644 bio-api/swagger_specs/informationOfGenes.yml create mode 100644 bio-api/swagger_specs/swagger_schemas.py diff --git a/bio-api/bioapi.py b/bio-api/bioapi.py index 8ad6c1d..66d912f 100755 --- a/bio-api/bioapi.py +++ b/bio-api/bioapi.py @@ -3,21 +3,19 @@ import json import gzip import logging -from turtle import title -from apispec import APISpec -from apispec.ext.marshmallow import MarshmallowPlugin -from apispec_webframeworks.flask import FlaskPlugin -from flask_apispec import FlaskApiSpec, doc, use_kwargs -from flask_swagger_ui import get_swaggerui_blueprint +from flask import Flask, jsonify, make_response, abort, render_template, request +# from apispec import APISpec +# from apispec.ext.marshmallow import MarshmallowPlugin +# from apispec_webframeworks.flask import FlaskPlugin +# from flask_swagger_ui import get_swaggerui_blueprint +from flasgger import Swagger, swag_from from db import get_mongo_connection from concurrent.futures import ThreadPoolExecutor import configparser from typing import List, Dict, Optional, Any -from flask import Flask, jsonify, make_response, abort, render_template, request from utils import map_gene from gprofiler import GProfiler -from schemas import swagger_schemas # Gets production flag @@ -650,55 +648,50 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) -> # Create an APISpec -spec = APISpec( - title="BioAPI", - version=VERSION, - openapi_version="2.0.0", - info=dict( - description=""" -## A powerful abstraction of genomics databases. -BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/). -To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""), - plugins=[FlaskPlugin(), MarshmallowPlugin()] -) +# spec = APISpec( +# title="BioAPI", +# version=VERSION, +# openapi_version="2.0.0", +# info=dict( +# description=""" +# ## A powerful abstraction of genomics databases. +# BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/). +# To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""), +# plugins=[FlaskPlugin()], +# ) def create_app(): # Creates and configures the app flask_app = Flask(__name__, instance_relative_config=True) - - # URL for exposing Swagger UI - SWAGGER_URL = '/api/docs' - # Spec API url or path - API_URL = '/static/apispec.json' - - # Config and Call factory function to create our blueprint - swagger_ui_config = { - 'docExpansion': False, # Avoid extended tags - 'displayRequestDuration': False, # Hide the duration of the request - 'tryItOutEnabled': False, # Enables testing functions without clicking "Try it out" - 'supportedSubmitMethods': ['get', 'post'], # Allows testing only GET and POST methods - 'validatorUrl': False, # Avoid online validation of documentation - "defaultModelsExpandDepth": -1, # Hide the Models section - 'filter': False, # Hide the method filter field + swagger_config = { + "headers": [ + ], + "openapi": "3.0.0", + "specs": [ + { + "endpoint": "swagger", + "route": "/apispec.json", + "rule_filter": lambda rule: True, # all in + "model_filter": lambda tag: True, # all in + } + ], + "title": "BioAPI", + "uiversion": 3, + "version": VERSION, + "termsOfService": False, + "swagger_ui": True, + "static_url_path": "/", + "specs_route": "/apidocs/", + "description": """ +## A powerful abstraction of genomics databases. +BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/). +To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)""" } - swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL, config=swagger_ui_config) - flask_app.register_blueprint(swaggerui_blueprint) - flask_app.config.update({'APISPEC_SPEC': spec, 'APISPEC_SWAGGER_UI_URL': SWAGGER_URL}) - - docs = FlaskApiSpec(flask_app) + swagger = Swagger(flask_app, config=swagger_config) # Endpoints - @flask_app.route(API_URL) - def swagger_json(): - """ Path to get OpenAPI Spec in ${API_URL}""" - schema = app.config['APISPEC_SPEC'].to_dict() - for path, methods in schema.get("paths", {}).items(): - methods.pop("options", None) - - return jsonify(schema) - @flask_app.route("/") def homepage(): return render_template('homepage.html', version=VERSION) @@ -710,20 +703,17 @@ def ping_ok(): return make_response(output, 200, headers) @flask_app.route("/gene-symbols", methods=['POST']) - @doc(description='Gene symbols validator', tags=['Genes'], consumes=["application/json"]) - @use_kwargs(args=swagger_schemas.GeneSymbolsRequestSchema, location="json") - def gene_symbols(gene_ids): + @swag_from("swagger_specs/geneSymbols.yml") + def gene_symbols(): """Receives a list of gene IDs in any standard and returns the standardized corresponding gene IDs. In case it is not found it returns an empty list for the specific not found gene.""" response = {} if request.method == 'POST': - if not request.is_json: - abort(400, "NO ES JSON!") body = request.get_json() if "gene_ids" not in body: abort(400, "gene_ids is mandatory") - # gene_ids = body['gene_ids'] + gene_ids = body['gene_ids'] if not isinstance(gene_ids, list): abort(400, "gene_ids must be a list") @@ -736,18 +726,19 @@ def gene_symbols(gene_ids): return make_response(response, 200, headers) @flask_app.route("/gene-symbols-finder/", methods=['GET']) - @doc(description='Gene symbols finder', tags=['Genes']) - @use_kwargs(args=swagger_schemas.GeneSymbolsFinderRequestSchema, location="query") - def gene_symbol_finder(query: str, limit: int|None): + # @doc(description='Gene symbols finder', tags=['Genes']) + # @use_kwargs(args=swagger_schemas.GeneSymbolsFinderRequestSchema, location="query") + @swag_from("swagger_specs/geneSymbolFinder.yml") + def gene_symbol_finder(): """Takes a string of any length and returns a list of genes that contain that search criteria.""" if "query" not in request.args: abort(400, "'query' parameter is mandatory") else: - query = request.args.get('query') # type: ignore + query = request.args.get('query') limit = 50 if "limit" in request.args: - limit_arg = request.args.get('limit') # type: ignore + limit_arg = request.args.get('limit') if limit_arg.isnumeric(): limit = int(limit_arg) else: @@ -760,6 +751,9 @@ def gene_symbol_finder(query: str, limit: int|None): abort(400, e) @flask_app.route("/information-of-genes", methods=['POST']) + # @doc(description='Genes information', tags=['Genes'], consumes=["application/json"]) + # @use_kwargs(args=swagger_schemas.InformationOfGenesRequestSchema, location="json") + @swag_from("swagger_specs/informationOfGenes.yml") def information_of_genes(): """Receives a list of gene IDs and returns information about them.""" body = request.get_json() # type: ignore @@ -777,7 +771,9 @@ def information_of_genes(): return make_response(response, 200, headers) @flask_app.route("/genes-of-its-group/", methods=['GET']) - def genes_in_the_same_group(gene_id): + # @doc(description='Gene Groups', tags=['Genes'], params={"gene_id": {"description": "Identifier of the gene for any database", "type": "string", "required": True}}) + @swag_from("swagger_specs/genesOfItsGroup.yml") + def genes_in_the_same_group(gene_id: str): response = {"gene_id": None, "groups": [], "locus_group": None, "locus_type": None} try: @@ -812,6 +808,10 @@ def genes_in_the_same_group(gene_id): return make_response(response, 200, headers) @flask_app.route("/pathway-genes//", methods=['GET']) + @swag_from("swagger_specs/genesOfMetabolicPathway.yml") + # @doc(description='Genes of a metabolic pathway', tags=['Genes'], + # 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"]}, + # "pathway_id": {"description": "Pathway identifier in the source database", "type": "string", "required": True, "example": "hsa00740"}}) def pathway_genes(pathway_source, pathway_id): if pathway_source.lower() not in PATHWAYS_SOURCES: abort(404, f'{pathway_source} is an invalid pathway source') @@ -820,16 +820,18 @@ def pathway_genes(pathway_source, pathway_id): return make_response(response, 200, headers) @flask_app.route("/pathways-in-common", methods=['POST']) - def pathways_in_common(): - body = request.get_json() # type: ignore - if "gene_ids" not in body: - abort(400, "gene_ids is mandatory") - - gene_ids = body['gene_ids'] - if not isinstance(gene_ids, list): - abort(400, "gene_ids must be a list") - if len(gene_ids) == 0: - abort(400, "gene_ids must contain at least one gene symbol") + # @doc(description='Metabolic pathways from different genes', tags=['Pathways'], consumes=["application/json"]) + # @use_kwargs(args=swagger_schemas.PathwaysInCommonRequestSchema, location="json") + def pathways_in_common(gene_ids: List[str]): + # body = request.get_json() # type: ignore + # if "gene_ids" not in body: + # abort(400, "gene_ids is mandatory") + + # gene_ids = body['gene_ids'] + # if not isinstance(gene_ids, list): + # abort(400, "gene_ids must be a list") + # if len(gene_ids) == 0: + # abort(400, "gene_ids must contain at least one gene symbol") pathways_tmp = [get_pathways_of_gene(gene) for gene in gene_ids] pathways_intersection = list(set.intersection(*map(set, pathways_tmp))) @@ -840,34 +842,36 @@ def pathways_in_common(): return make_response(response, 200, headers) @flask_app.route("/expression-of-genes", methods=['POST']) - def expression_data_from_gtex(): - body = request.get_json() # type: ignore + # @doc(description='Gets gene expression in healthy tissue', tags=['Expression Data'], consumes=["application/json"]) + # @use_kwargs(args=swagger_schemas.ExpressionOfGenesRequestSchema, location="json") + def expression_data_from_gtex(gene_ids: list[str], tissue: str, type: str): + # body = request.get_json() # type: ignore - if "gene_ids" not in body: - abort(400, "gene_ids is mandatory") + # if "gene_ids" not in body: + # abort(400, "gene_ids is mandatory") - gene_ids = body['gene_ids'] - if not isinstance(gene_ids, list): - abort(400, "gene_ids must be a list") + # gene_ids = body['gene_ids'] + # if not isinstance(gene_ids, list): + # abort(400, "gene_ids must be a list") - if len(gene_ids) == 0: - abort(400, "gene_ids must contain at least one gene symbol") + # if len(gene_ids) == 0: + # abort(400, "gene_ids must contain at least one gene symbol") - if "tissue" not in body: - abort(400, "tissue is mandatory") + # if "tissue" not in body: + # abort(400, "tissue is mandatory") - tissue = body['tissue'] - if "type" in body: - if body['type'] not in ["gzip", "json"]: - abort(400, "allowed values for the 'type' key are 'json' or 'gzip'") - else: - type_response = body['type'] - else: - type_response = 'json' + # tissue = body['tissue'] + # if "type" in body: + # if body['type'] not in ["gzip", "json"]: + # abort(400, "allowed values for the 'type' key are 'json' or 'gzip'") + # else: + # type_response = body['type'] + # else: + # type_response = 'json' expression_data = get_expression_from_gtex(tissue, gene_ids) - if type_response == "gzip": + if type == "gzip": content = gzip.compress(json.dumps( expression_data).encode('utf8'), 5) response = make_response(content) @@ -877,7 +881,9 @@ def expression_data_from_gtex(): return jsonify(expression_data) @flask_app.route("/genes-to-terms", methods=['POST']) - def genes_to_go_terms(): + # @doc(description='Gene Ontology terms related to a list of genes', tags=['Gene Ontology'], consumes=["application/json"]) + # @use_kwargs(args=swagger_schemas.GenesToTermsRequestSchema, location="json") + 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]): """Receives a list of genes and returns the related terms""" valid_filter_types = ["union", "intersection", "enrichment"] valid_ontology_types = ["biological_process", @@ -1011,17 +1017,19 @@ def related_terms(): return jsonify(response) @flask_app.route("/information-of-oncokb", methods=['POST']) - def oncokb_data(): - body = request.get_json() # type: ignore + # @doc(description='Therapies and actionable genes in cancer', tags=['Genes'], consumes=["application/json"]) + # @use_kwargs(args=swagger_schemas.InformationOfOncokbRequestSchema, location="json") + def oncokb_data(gene_ids: list[str], query: str): + # body = request.get_json() # type: ignore - if "gene_ids" not in body: - abort(400, "gene_ids is mandatory") + # if "gene_ids" not in body: + # abort(400, "gene_ids is mandatory") - gene_ids = body['gene_ids'] - query = "" if "query" not in body else body['query'] + # gene_ids = body['gene_ids'] + # query = "" if "query" not in body else body['query'] - if not isinstance(gene_ids, list): - abort(400, "gene_ids must be a list") + # if not isinstance(gene_ids, list): + # abort(400, "gene_ids must be a list") if len(gene_ids) == 0: abort(400, "gene_ids must contain at least one gene symbol") @@ -1083,10 +1091,6 @@ def bad_request(e): def not_found(e): return jsonify(error=str(e)), 404 - with flask_app.test_request_context(): - docs.register(target=gene_symbols) - docs.register(target=gene_symbol_finder) - return flask_app diff --git a/bio-api/schemas/swagger_schemas.py b/bio-api/schemas/swagger_schemas.py deleted file mode 100644 index 8563fed..0000000 --- a/bio-api/schemas/swagger_schemas.py +++ /dev/null @@ -1,17 +0,0 @@ -from marshmallow import Schema, fields - - -class GeneSymbolsRequestSchema(Schema): - gene_ids = fields.List(fields.String(), required=True, example=["FANCS", "BRCC1"]) - - -class GeneSymbolsFinderRequestSchema(Schema): - query = fields.String( - required=True, - description="gene search string", - example="TP" - ) - limit = fields.Integer( - missing=50, - description="number of elements returned by the service. Default 50." - ) diff --git a/bio-api/swagger_specs/geneSymbolFinder.yml b/bio-api/swagger_specs/geneSymbolFinder.yml new file mode 100644 index 0000000..973ab14 --- /dev/null +++ b/bio-api/swagger_specs/geneSymbolFinder.yml @@ -0,0 +1,23 @@ +tags: + - Gene Nomenclature +summary: "Gene symbol finder" +description: "Service that takes a string of any length and returns a list of genes that contain that search criteria." +operationId: "Gene symbol finder" +parameters: + - in: query + name: query + description: "Gene search string." + required: true + schema: + type: string + example: "TP" + - in: query + name: limit + description: "Limit the number of results returned (Default 50)." + required: false + schema: + type: integer + example: 10 +responses: + 200: + description: "List of genes containing that search criterion in bioinformatics databases." diff --git a/bio-api/swagger_specs/geneSymbols.yml b/bio-api/swagger_specs/geneSymbols.yml new file mode 100644 index 0000000..0efd424 --- /dev/null +++ b/bio-api/swagger_specs/geneSymbols.yml @@ -0,0 +1,21 @@ +tags: + - Gene Nomenclature +summary: "Gene symbol validator" +description: "Searches the identifier of a list of genes of different genomics databases and returns the approved symbols according to HGNC nomenclature." +operationId: "Gene symbol" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["FANCS", "BRCC1"] +responses: + 200: + description: "HGNC approved gene symbols." diff --git a/bio-api/swagger_specs/genesOfItsGroup.yml b/bio-api/swagger_specs/genesOfItsGroup.yml new file mode 100644 index 0000000..bb60e6f --- /dev/null +++ b/bio-api/swagger_specs/genesOfItsGroup.yml @@ -0,0 +1,16 @@ +tags: + - Genes information +summary: "Gene Groups" +description: "Gets the identifier of a gene, validates it and then returns the group of genes to which it belongs according to HGNC, and all the other genes that belong to the same group." +operationId: "Gene Groups" +parameters: + - in: path + name: gene_id + description: "Identifier of the gene for any database." + required: true + schema: + type: string + example: "EGFR" +responses: + 200: + description: "Group of genes to which gene_id belongs according to HGNC." diff --git a/bio-api/swagger_specs/genesOfMetabolicPathway.yml b/bio-api/swagger_specs/genesOfMetabolicPathway.yml new file mode 100644 index 0000000..306fce3 --- /dev/null +++ b/bio-api/swagger_specs/genesOfMetabolicPathway.yml @@ -0,0 +1,35 @@ +tags: + - Metabolic Pathway +summary: "Genes of a metabolic pathway" +description: "Get the list of genes that are involved in a pathway for a given database." +operationId: "Genes of Metabolic Pathway" +parameters: + - in: path + name: pathway_source + description: "Database to query." + required: true + schema: + type: string + enum: + - kegg + - biocarta + - ehmn + - humancyc + - inoh + - netpath + - pid + - reactome + - smpdb + - signalink + - wikipathways + example: "kegg" + - in: path + name: pathway_id + description: "Pathway identifier in the source database." + required: true + schema: + type: string + example: "hsa00740" +responses: + 200: + description: "List of genes involved in the metabolic pathway." \ No newline at end of file diff --git a/bio-api/swagger_specs/informationOfGenes.yml b/bio-api/swagger_specs/informationOfGenes.yml new file mode 100644 index 0000000..5dd6325 --- /dev/null +++ b/bio-api/swagger_specs/informationOfGenes.yml @@ -0,0 +1,21 @@ +tags: + - Genes information +summary: "Genes information" +description: "From a list of valid genes, it obtains different information for the human reference genomes GRCh38 and GRCh37." +operationId: "Genes information" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["MC1R", "ALK"] +responses: + 200: + description: "Gene information" diff --git a/bio-api/swagger_specs/swagger_schemas.py b/bio-api/swagger_specs/swagger_schemas.py new file mode 100644 index 0000000..f75e9ca --- /dev/null +++ b/bio-api/swagger_specs/swagger_schemas.py @@ -0,0 +1,75 @@ +from flask_marshmallow import Schema +from marshmallow import fields, validate + + +class GeneSymbolsRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="List of valid genes identifiers", + required=True, + example=["FANCS", "BRCC1"]) + + +class GeneSymbolsFinderRequestSchema(Schema): + query = fields.String( + required=True, + description="gene search string", + example="TP" + ) + limit = fields.Integer( + missing=50, + description="number of elements returned by the service. Default 50." + ) + + +class InformationOfGenesRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="List of valid genes identifiers", + required=True, + example=["TP53", "MC1R"]) + + +class PathwaysInCommonRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="List of valid genes identifiers", + required=True, + example=["HLA-B", "BRAF"]) + + +class ExpressionOfGenesRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="List of valid genes identifiers", + required=True, + example=["BRCA1", "BRCA2"]) + tissue = fields.String(description="Healthy tissue from which you want to get the expression values.", + required=True, example="Skin", + validate=validate.OneOf(["Adipose Tissue", "Adrenal Gland", "Bladder", "Blood", "Blood Vessel", "Brain", "Breast", "Cervix Uteri", "Colon", "Esophagus", "Fallopian Tube", "Heart", "Kidney", "Liver", "Lung", "Muscle", "Nerve", "Ovary", "Pancreas", "Pituitary", "Prostate", "Salivary Gland", "Skin", "Small Intestine", "Spleen", "Stomach", "Testis", "Thyroid", "Uterus", "Vagina"])) + type = fields.String(description="Type of response format: json or gzip. Default: json", + required=False, missing="json", validate=validate.OneOf(["json", "gzip"])) + + +class GenesToTermsRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="List of genes for which you want to get the terms in common", + required=True, + example=["TMCO4"]) + filter_type = fields.String(description="Type of flter: intersection, union or enrichment", + required=False, example="intersection", validate=validate.OneOf(["intersection", "union", "enrichment"])) + p_value_threshold = fields.Float(description="Just for enrichment filter: It's the p-value threshold. Not recommended to set it higher than 0.05.", dump_only=True, required=False, example=0.05 ) + correction_method = fields.String(description="Just for enrichment filter: The enrichment default correction method is analytical. Alternatively, one may select bonferroni correction or false_discovery_rate (Benjamini-Hochberg FDR).", + required=False, example="analytical", dump_only=True, validate=validate.OneOf(["analytical", "bonferroni", "false_discovery_rate"])) + relation_type = fields.List(fields.String(), + description="Filters the relation between genes and terms. By default it's ['enables','involved_in','part_of','located_in']. It should always be a list containing any permutation of the allowed relations. Only valid on filter_type intersection and union.", + required=False, + example=['enables', 'involved_in', 'part_of', 'located_in']) + ontology_type = fields.List(fields.String(), + description="Filters the ontology type of the terms in the response. By default it's ['biological_process', 'molecular_function', 'cellular_component']. It should always be a list containing any permutation of the 3 ontologies.", + required=False, + example=['biological_process', 'molecular_function', 'cellular_component']) + + +class InformationOfOncokbRequestSchema(Schema): + gene_ids = fields.List(fields.String(), + description="Llist of genes for which you want to get the information from the OncoKB database", + required=True, + example=["HLA-B", "BRAF"]) + query = fields.String(description="Parameter used to show only the results that match it", required=False, missing="") diff --git a/bio-api/templates/homepage.html b/bio-api/templates/homepage.html index 4b7e96e..9bce328 100644 --- a/bio-api/templates/homepage.html +++ b/bio-api/templates/homepage.html @@ -14,6 +14,6 @@

BioAPI {{ version }}

A powerful abstraction of genomics databases

-

Documentation

+

Documentation

\ No newline at end of file diff --git a/config/bioapi_conf/requirements.txt b/config/bioapi_conf/requirements.txt index 666d9ee..359693f 100755 --- a/config/bioapi_conf/requirements.txt +++ b/config/bioapi_conf/requirements.txt @@ -1,15 +1,15 @@ pymongo==4.6.1 -Flask==3.0.0 +Flask==3.1.0 flask-cors==5.0.0 gunicorn==21.2.0 ConfigParser==6.0.0 -pytest==7.1.2 +pytest==8.3.3 tqdm==4.66.1 gprofiler-official==1.0.0 Werkzeug==3.0.1 flask_swagger_ui==4.11.1 apispec==6.7.1 -flask-apispec==0.11.4 apispec-webframeworks==1.2.0 marshmallow==3.23.1 -setuptools==75.5.0 \ No newline at end of file +setuptools==75.5.0 +flasgger==0.9.7.1 \ No newline at end of file From 67ea793d392628a021131d55d9774d879c0a9305 Mon Sep 17 00:00:00 2001 From: mauricio Date: Tue, 3 Dec 2024 19:52:19 -0300 Subject: [PATCH 4/5] swagger complete --- DEPLOYING.md | 3 +- bio-api/bioapi.py | 125 +++++++----------- .../cancerDrugsRelatedToGenes.yml | 22 +++ bio-api/swagger_specs/drugsRegulatingGene.yml | 16 +++ bio-api/swagger_specs/expressionOfGenes.yml | 61 +++++++++ bio-api/swagger_specs/genesOfItsGroup.yml | 2 +- bio-api/swagger_specs/genesToTerms.yml | 48 +++++++ bio-api/swagger_specs/informationOfGenes.yml | 2 +- bio-api/swagger_specs/informationOfOncokb.yml | 21 +++ bio-api/swagger_specs/pathwaysInCommon.yml | 21 +++ bio-api/swagger_specs/relatedTerms.yml | 44 ++++++ bio-api/swagger_specs/stringRelations.yml | 25 ++++ bio-api/swagger_specs/swagger_schemas.py | 75 ----------- 13 files changed, 311 insertions(+), 154 deletions(-) create mode 100644 bio-api/swagger_specs/cancerDrugsRelatedToGenes.yml create mode 100644 bio-api/swagger_specs/drugsRegulatingGene.yml create mode 100644 bio-api/swagger_specs/expressionOfGenes.yml create mode 100644 bio-api/swagger_specs/genesToTerms.yml create mode 100644 bio-api/swagger_specs/informationOfOncokb.yml create mode 100644 bio-api/swagger_specs/pathwaysInCommon.yml create mode 100644 bio-api/swagger_specs/relatedTerms.yml create mode 100644 bio-api/swagger_specs/stringRelations.yml delete mode 100644 bio-api/swagger_specs/swagger_schemas.py diff --git a/DEPLOYING.md b/DEPLOYING.md index 7b0c7c2..f086f77 100644 --- a/DEPLOYING.md +++ b/DEPLOYING.md @@ -32,7 +32,6 @@ Below are the steps to perform a production deployment of BioAPI. BioAPI uses three genomic databases for its operation. These databases must be loaded in MongoDB. You can import all the databases in two ways: - ### Import using public DB backup (recommended) To import all databases in MongoDB: @@ -67,7 +66,6 @@ To import all databases in MongoDB: 4. Stop services with the command `docker compose -f docker-compose.dev.yml down` 5. Roll up the changes in the `docker-compose.dev.yml` file to remove the backup file from the `volumes` section. Restart all the services again. - ### Manually import the different databases Alternatively (but **not recommended** due to high computational demands) you can run a separate ETL process to download from source, process and import the databases into MongoDB. @@ -101,6 +99,7 @@ docker-compose up -d ``` By default, BioAPI runs on `localhost:8000`. +Test BioAPI with Swagger on `localhost:8000/apidocs` If you want to stop all services, you can execute: diff --git a/bio-api/bioapi.py b/bio-api/bioapi.py index 66d912f..2e52b35 100755 --- a/bio-api/bioapi.py +++ b/bio-api/bioapi.py @@ -5,10 +5,6 @@ import logging from flask import Flask, jsonify, make_response, abort, render_template, request -# from apispec import APISpec -# from apispec.ext.marshmallow import MarshmallowPlugin -# from apispec_webframeworks.flask import FlaskPlugin -# from flask_swagger_ui import get_swaggerui_blueprint from flasgger import Swagger, swag_from from db import get_mongo_connection from concurrent.futures import ThreadPoolExecutor @@ -647,23 +643,10 @@ def associated_string_genes(gene_symbol: str, min_combined_score: int = 400) -> return res -# Create an APISpec -# spec = APISpec( -# title="BioAPI", -# version=VERSION, -# openapi_version="2.0.0", -# info=dict( -# description=""" -# ## A powerful abstraction of genomics databases. -# BioAPI is part of the Multiomix project. For more information, visit our [website](https://omicsdatascience.org/). -# To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)"""), -# plugins=[FlaskPlugin()], -# ) - - def create_app(): # Creates and configures the app flask_app = Flask(__name__, instance_relative_config=True) + swagger_config = { "headers": [ ], @@ -672,8 +655,8 @@ def create_app(): { "endpoint": "swagger", "route": "/apispec.json", - "rule_filter": lambda rule: True, # all in - "model_filter": lambda tag: True, # all in + "rule_filter": lambda rule: True, + "model_filter": lambda tag: True } ], "title": "BioAPI", @@ -726,8 +709,6 @@ def gene_symbols(): return make_response(response, 200, headers) @flask_app.route("/gene-symbols-finder/", methods=['GET']) - # @doc(description='Gene symbols finder', tags=['Genes']) - # @use_kwargs(args=swagger_schemas.GeneSymbolsFinderRequestSchema, location="query") @swag_from("swagger_specs/geneSymbolFinder.yml") def gene_symbol_finder(): """Takes a string of any length and returns a list of genes that contain that search criteria.""" @@ -751,8 +732,6 @@ def gene_symbol_finder(): abort(400, e) @flask_app.route("/information-of-genes", methods=['POST']) - # @doc(description='Genes information', tags=['Genes'], consumes=["application/json"]) - # @use_kwargs(args=swagger_schemas.InformationOfGenesRequestSchema, location="json") @swag_from("swagger_specs/informationOfGenes.yml") def information_of_genes(): """Receives a list of gene IDs and returns information about them.""" @@ -771,7 +750,6 @@ def information_of_genes(): return make_response(response, 200, headers) @flask_app.route("/genes-of-its-group/", methods=['GET']) - # @doc(description='Gene Groups', tags=['Genes'], params={"gene_id": {"description": "Identifier of the gene for any database", "type": "string", "required": True}}) @swag_from("swagger_specs/genesOfItsGroup.yml") def genes_in_the_same_group(gene_id: str): response = {"gene_id": None, "groups": [], @@ -809,9 +787,6 @@ def genes_in_the_same_group(gene_id: str): @flask_app.route("/pathway-genes//", methods=['GET']) @swag_from("swagger_specs/genesOfMetabolicPathway.yml") - # @doc(description='Genes of a metabolic pathway', tags=['Genes'], - # 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"]}, - # "pathway_id": {"description": "Pathway identifier in the source database", "type": "string", "required": True, "example": "hsa00740"}}) def pathway_genes(pathway_source, pathway_id): if pathway_source.lower() not in PATHWAYS_SOURCES: abort(404, f'{pathway_source} is an invalid pathway source') @@ -820,18 +795,17 @@ def pathway_genes(pathway_source, pathway_id): return make_response(response, 200, headers) @flask_app.route("/pathways-in-common", methods=['POST']) - # @doc(description='Metabolic pathways from different genes', tags=['Pathways'], consumes=["application/json"]) - # @use_kwargs(args=swagger_schemas.PathwaysInCommonRequestSchema, location="json") - def pathways_in_common(gene_ids: List[str]): - # body = request.get_json() # type: ignore - # if "gene_ids" not in body: - # abort(400, "gene_ids is mandatory") - - # gene_ids = body['gene_ids'] - # if not isinstance(gene_ids, list): - # abort(400, "gene_ids must be a list") - # if len(gene_ids) == 0: - # abort(400, "gene_ids must contain at least one gene symbol") + @swag_from("swagger_specs/pathwaysInCommon.yml") + def pathways_in_common(): + body = request.get_json() # type: ignore + if "gene_ids" not in body: + abort(400, "gene_ids is mandatory") + + gene_ids = body['gene_ids'] + if not isinstance(gene_ids, list): + abort(400, "gene_ids must be a list") + if len(gene_ids) == 0: + abort(400, "gene_ids must contain at least one gene symbol") pathways_tmp = [get_pathways_of_gene(gene) for gene in gene_ids] pathways_intersection = list(set.intersection(*map(set, pathways_tmp))) @@ -842,36 +816,35 @@ def pathways_in_common(gene_ids: List[str]): return make_response(response, 200, headers) @flask_app.route("/expression-of-genes", methods=['POST']) - # @doc(description='Gets gene expression in healthy tissue', tags=['Expression Data'], consumes=["application/json"]) - # @use_kwargs(args=swagger_schemas.ExpressionOfGenesRequestSchema, location="json") - def expression_data_from_gtex(gene_ids: list[str], tissue: str, type: str): - # body = request.get_json() # type: ignore + @swag_from("swagger_specs/expressionOfGenes.yml") + def expression_data_from_gtex(): + body = request.get_json() # type: ignore - # if "gene_ids" not in body: - # abort(400, "gene_ids is mandatory") + if "gene_ids" not in body: + abort(400, "gene_ids is mandatory") - # gene_ids = body['gene_ids'] - # if not isinstance(gene_ids, list): - # abort(400, "gene_ids must be a list") + gene_ids = body['gene_ids'] + if not isinstance(gene_ids, list): + abort(400, "gene_ids must be a list") - # if len(gene_ids) == 0: - # abort(400, "gene_ids must contain at least one gene symbol") + if len(gene_ids) == 0: + abort(400, "gene_ids must contain at least one gene symbol") - # if "tissue" not in body: - # abort(400, "tissue is mandatory") + if "tissue" not in body: + abort(400, "tissue is mandatory") - # tissue = body['tissue'] - # if "type" in body: - # if body['type'] not in ["gzip", "json"]: - # abort(400, "allowed values for the 'type' key are 'json' or 'gzip'") - # else: - # type_response = body['type'] - # else: - # type_response = 'json' + tissue = body['tissue'] + if "type" in body: + if body['type'] not in ["gzip", "json"]: + abort(400, "allowed values for the 'type' key are 'json' or 'gzip'") + else: + type_response = body['type'] + else: + type_response = 'json' expression_data = get_expression_from_gtex(tissue, gene_ids) - if type == "gzip": + if type_response == "gzip": content = gzip.compress(json.dumps( expression_data).encode('utf8'), 5) response = make_response(content) @@ -881,9 +854,8 @@ def expression_data_from_gtex(gene_ids: list[str], tissue: str, type: str): return jsonify(expression_data) @flask_app.route("/genes-to-terms", methods=['POST']) - # @doc(description='Gene Ontology terms related to a list of genes', tags=['Gene Ontology'], consumes=["application/json"]) - # @use_kwargs(args=swagger_schemas.GenesToTermsRequestSchema, location="json") - 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]): + @swag_from("swagger_specs/genesToTerms.yml") + def genes_to_go_terms(): """Receives a list of genes and returns the related terms""" valid_filter_types = ["union", "intersection", "enrichment"] valid_ontology_types = ["biological_process", @@ -970,6 +942,7 @@ def genes_to_go_terms(gene_ids: list[str], filter_type: str, p_value_threshold: return jsonify(response) @flask_app.route("/related-terms", methods=['POST']) + @swag_from("swagger_specs/relatedTerms.yml") def related_terms(): """Receives a term and returns the related terms""" valid_ontology_types = ["biological_process", @@ -1017,19 +990,18 @@ def related_terms(): return jsonify(response) @flask_app.route("/information-of-oncokb", methods=['POST']) - # @doc(description='Therapies and actionable genes in cancer', tags=['Genes'], consumes=["application/json"]) - # @use_kwargs(args=swagger_schemas.InformationOfOncokbRequestSchema, location="json") - def oncokb_data(gene_ids: list[str], query: str): - # body = request.get_json() # type: ignore + @swag_from("swagger_specs/informationOfOncokb.yml") + def oncokb_data(): + body = request.get_json() # type: ignore - # if "gene_ids" not in body: - # abort(400, "gene_ids is mandatory") + if "gene_ids" not in body: + abort(400, "gene_ids is mandatory") - # gene_ids = body['gene_ids'] - # query = "" if "query" not in body else body['query'] + gene_ids = body['gene_ids'] + query = "" if "query" not in body else body['query'] - # if not isinstance(gene_ids, list): - # abort(400, "gene_ids must be a list") + if not isinstance(gene_ids, list): + abort(400, "gene_ids must be a list") if len(gene_ids) == 0: abort(400, "gene_ids must contain at least one gene symbol") @@ -1039,6 +1011,7 @@ def oncokb_data(gene_ids: list[str], query: str): return jsonify(data) @flask_app.route("/drugs-pharm-gkb", methods=['POST']) + @swag_from("swagger_specs/cancerDrugsRelatedToGenes.yml") def cancer_drugs_related_to_genes(): """Receives genes and returns the related drugs""" response = {} @@ -1053,6 +1026,7 @@ def cancer_drugs_related_to_genes(): return jsonify(response) @flask_app.route("/string-relations", methods=['POST']) + @swag_from("swagger_specs/stringRelations.yml") def string_relations_to_gene(): body = request.get_json() optionals = {} @@ -1070,6 +1044,7 @@ def string_relations_to_gene(): return jsonify(res) @flask_app.route("/drugs-regulating-gene/", methods=['GET']) + @swag_from("swagger_specs/drugsRegulatingGene.yml") def drugs_regulating_gene(gene_id): return { "link": "https://go.drugbank.com/pharmaco/transcriptomics?q%5Bg%5B0%5D%5D%5Bm%5D=or&q%5Bg%5B0%5D%5D" diff --git a/bio-api/swagger_specs/cancerDrugsRelatedToGenes.yml b/bio-api/swagger_specs/cancerDrugsRelatedToGenes.yml new file mode 100644 index 0000000..3048334 --- /dev/null +++ b/bio-api/swagger_specs/cancerDrugsRelatedToGenes.yml @@ -0,0 +1,22 @@ +tags: + - Accionable Genes and Drugs +summary: "Cancer related drugs" +description: "Gets a list of drugs from the PharmGKB database related to a list of genes." +operationId: "Cancer related drugs" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["JAK2", "EGFR"] +responses: + 200: + description: "List of all information related to drugs and genes." + diff --git a/bio-api/swagger_specs/drugsRegulatingGene.yml b/bio-api/swagger_specs/drugsRegulatingGene.yml new file mode 100644 index 0000000..3b5dc71 --- /dev/null +++ b/bio-api/swagger_specs/drugsRegulatingGene.yml @@ -0,0 +1,16 @@ +tags: + - Accionable Genes and Drugs +summary: "Drugs that regulate a gene expression." +description: "Service that takes gene symbol and returns a link to https://go.drugbank.com with all the drugs that upregulate and down regulate its expresion. Useful for embeding." +operationId: "Drugs that regulate genetic expression." +parameters: + - in: path + name: gene_id + description: "Identifier of the gene." + required: true + schema: + type: string + example: "TP53" +responses: + 200: + description: "URL that points to the information on the DrugBank website." \ No newline at end of file diff --git a/bio-api/swagger_specs/expressionOfGenes.yml b/bio-api/swagger_specs/expressionOfGenes.yml new file mode 100644 index 0000000..4ad24d5 --- /dev/null +++ b/bio-api/swagger_specs/expressionOfGenes.yml @@ -0,0 +1,61 @@ +tags: + - Gene Expression +summary: "Gene expression" +description: "Gets gene expression in healthy tissue." +operationId: "Gene expression in healthy tissues" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["BRCA1", "BRCA2"] + tissue: + type: string + enum: + - Adipose Tissue + - Adrenal Gland + - Bladder + - Blood + - Blood Vessel + - Brain + - Breast + - Cervix Uteri + - Colon + - Esophagus + - Fallopian Tube + - Heart + - Kidney + - Liver + - Lung + - Muscle + - Nerve + - Ovary + - Pancreas + - Pituitary + - Prostate + - Salivary Gland + - Skin + - Small Intestine + - Spleen + - Stomach + - Testis + - Thyroid + - Uterus + - Vagina + example: "Skin" + type: + type: string + enum: + - json + - gzip + example: "gzip" +responses: + 200: + description: "Expression values ​​of each gene according to the GTEx database." \ No newline at end of file diff --git a/bio-api/swagger_specs/genesOfItsGroup.yml b/bio-api/swagger_specs/genesOfItsGroup.yml index bb60e6f..1ae7c0b 100644 --- a/bio-api/swagger_specs/genesOfItsGroup.yml +++ b/bio-api/swagger_specs/genesOfItsGroup.yml @@ -1,5 +1,5 @@ tags: - - Genes information + - Genes Information summary: "Gene Groups" description: "Gets the identifier of a gene, validates it and then returns the group of genes to which it belongs according to HGNC, and all the other genes that belong to the same group." operationId: "Gene Groups" diff --git a/bio-api/swagger_specs/genesToTerms.yml b/bio-api/swagger_specs/genesToTerms.yml new file mode 100644 index 0000000..f77138f --- /dev/null +++ b/bio-api/swagger_specs/genesToTerms.yml @@ -0,0 +1,48 @@ +tags: + - Gene Ontology +summary: "Gene Ontology terms related to a list of genes" +description: "Gets the list of related terms for a list of genes." +operationId: "Gene ontology terms" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["TMCO4"] + filter_type: + type: string + enum: + - intersection + - union + - enrichment + example: "intersection" + relation_type: + type: array + items: + type: string + enum: + - enables + - involved_in + - part_of + - located_in + example: ["enables","involved_in","part_of","located_in"] + ontology_type: + type: array + items: + type: string + enum: + - biological_process + - molecular_function + - cellular_component + example: ["biological_process", "molecular_function", "cellular_component"] +responses: + 200: + description: "The response you get is a list of GO terms that meet the query conditions." + diff --git a/bio-api/swagger_specs/informationOfGenes.yml b/bio-api/swagger_specs/informationOfGenes.yml index 5dd6325..acd7cc8 100644 --- a/bio-api/swagger_specs/informationOfGenes.yml +++ b/bio-api/swagger_specs/informationOfGenes.yml @@ -1,5 +1,5 @@ tags: - - Genes information + - Genes Information summary: "Genes information" description: "From a list of valid genes, it obtains different information for the human reference genomes GRCh38 and GRCh37." operationId: "Genes information" diff --git a/bio-api/swagger_specs/informationOfOncokb.yml b/bio-api/swagger_specs/informationOfOncokb.yml new file mode 100644 index 0000000..83d3069 --- /dev/null +++ b/bio-api/swagger_specs/informationOfOncokb.yml @@ -0,0 +1,21 @@ +tags: + - Accionable Genes and Drugs +summary: "Therapies and Actionable Genes in Cancer" +description: "This service retrieves information on FDA-approved precision oncology therapies, actionable genes, and drugs obtained from the OncoKB database, at a therapeutic, diagnostic, and prognostic level." +operationId: "Therapies and Actionable Genes in Cancer" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + items: + type: string + example: ["ATM", "EGFR"] +responses: + 200: + description: "OncoKB database information for each gene." + diff --git a/bio-api/swagger_specs/pathwaysInCommon.yml b/bio-api/swagger_specs/pathwaysInCommon.yml new file mode 100644 index 0000000..2c06450 --- /dev/null +++ b/bio-api/swagger_specs/pathwaysInCommon.yml @@ -0,0 +1,21 @@ +tags: + - Metabolic Pathway +summary: "Metabolic pathways from different genes" +description: "Gets the common pathways for a list of genes." +operationId: "Common pathways for genes" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_ids: + type: array + description: "List of gene identifiers." + items: + type: string + example: ["HLA-B", "BRAF"] +responses: + 200: + description: "A list of pathways that include the genes sent as parameters." diff --git a/bio-api/swagger_specs/relatedTerms.yml b/bio-api/swagger_specs/relatedTerms.yml new file mode 100644 index 0000000..e2812cf --- /dev/null +++ b/bio-api/swagger_specs/relatedTerms.yml @@ -0,0 +1,44 @@ +tags: + - Gene Ontology +summary: "Gene Ontology terms related to another specific term" +description: "Gets the list of related terms to a term." +operationId: "Related gene ontology terms" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + term_id: + type: string + example: "0000079" + relations: + type: array + items: + type: string + enum: + - part_of + - regulates + - has_part + example: ["part_of","regulates","has_part"] + ontology_type: + type: array + items: + type: string + enum: + - biological_process + - molecular_function + - cellular_component + example: ["biological_process", "molecular_function", "cellular_component"] + general_depth: + type: int + example: 5 + to_root: + type: int + example: 0 + +responses: + 200: + description: "The response you get is a list of GO terms that meet the query conditions." + diff --git a/bio-api/swagger_specs/stringRelations.yml b/bio-api/swagger_specs/stringRelations.yml new file mode 100644 index 0000000..03d6bdc --- /dev/null +++ b/bio-api/swagger_specs/stringRelations.yml @@ -0,0 +1,25 @@ +tags: + - Functional interactions +summary: "Predicted functional associations network" +description: "For a given gene, this service gets from the String database a list of genes and their relationships to it." +operationId: "Cancer related drugs" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + gene_id: + type: string + description: "Gene identifier." + items: + type: string + example: "MX2" + min_combined_score: + type: int + example: 976 +responses: + 200: + description: "List of genes and their relationships according to the String database." + diff --git a/bio-api/swagger_specs/swagger_schemas.py b/bio-api/swagger_specs/swagger_schemas.py deleted file mode 100644 index f75e9ca..0000000 --- a/bio-api/swagger_specs/swagger_schemas.py +++ /dev/null @@ -1,75 +0,0 @@ -from flask_marshmallow import Schema -from marshmallow import fields, validate - - -class GeneSymbolsRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="List of valid genes identifiers", - required=True, - example=["FANCS", "BRCC1"]) - - -class GeneSymbolsFinderRequestSchema(Schema): - query = fields.String( - required=True, - description="gene search string", - example="TP" - ) - limit = fields.Integer( - missing=50, - description="number of elements returned by the service. Default 50." - ) - - -class InformationOfGenesRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="List of valid genes identifiers", - required=True, - example=["TP53", "MC1R"]) - - -class PathwaysInCommonRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="List of valid genes identifiers", - required=True, - example=["HLA-B", "BRAF"]) - - -class ExpressionOfGenesRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="List of valid genes identifiers", - required=True, - example=["BRCA1", "BRCA2"]) - tissue = fields.String(description="Healthy tissue from which you want to get the expression values.", - required=True, example="Skin", - validate=validate.OneOf(["Adipose Tissue", "Adrenal Gland", "Bladder", "Blood", "Blood Vessel", "Brain", "Breast", "Cervix Uteri", "Colon", "Esophagus", "Fallopian Tube", "Heart", "Kidney", "Liver", "Lung", "Muscle", "Nerve", "Ovary", "Pancreas", "Pituitary", "Prostate", "Salivary Gland", "Skin", "Small Intestine", "Spleen", "Stomach", "Testis", "Thyroid", "Uterus", "Vagina"])) - type = fields.String(description="Type of response format: json or gzip. Default: json", - required=False, missing="json", validate=validate.OneOf(["json", "gzip"])) - - -class GenesToTermsRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="List of genes for which you want to get the terms in common", - required=True, - example=["TMCO4"]) - filter_type = fields.String(description="Type of flter: intersection, union or enrichment", - required=False, example="intersection", validate=validate.OneOf(["intersection", "union", "enrichment"])) - p_value_threshold = fields.Float(description="Just for enrichment filter: It's the p-value threshold. Not recommended to set it higher than 0.05.", dump_only=True, required=False, example=0.05 ) - correction_method = fields.String(description="Just for enrichment filter: The enrichment default correction method is analytical. Alternatively, one may select bonferroni correction or false_discovery_rate (Benjamini-Hochberg FDR).", - required=False, example="analytical", dump_only=True, validate=validate.OneOf(["analytical", "bonferroni", "false_discovery_rate"])) - relation_type = fields.List(fields.String(), - description="Filters the relation between genes and terms. By default it's ['enables','involved_in','part_of','located_in']. It should always be a list containing any permutation of the allowed relations. Only valid on filter_type intersection and union.", - required=False, - example=['enables', 'involved_in', 'part_of', 'located_in']) - ontology_type = fields.List(fields.String(), - description="Filters the ontology type of the terms in the response. By default it's ['biological_process', 'molecular_function', 'cellular_component']. It should always be a list containing any permutation of the 3 ontologies.", - required=False, - example=['biological_process', 'molecular_function', 'cellular_component']) - - -class InformationOfOncokbRequestSchema(Schema): - gene_ids = fields.List(fields.String(), - description="Llist of genes for which you want to get the information from the OncoKB database", - required=True, - example=["HLA-B", "BRAF"]) - query = fields.String(description="Parameter used to show only the results that match it", required=False, missing="") From 264d0d3e34a3bc489f068db3d7cbd020ae762d27 Mon Sep 17 00:00:00 2001 From: mauricio Date: Wed, 11 Dec 2024 18:48:02 -0300 Subject: [PATCH 5/5] small change for pep8 --- bio-api/bioapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bio-api/bioapi.py b/bio-api/bioapi.py index 2e52b35..dba63d1 100755 --- a/bio-api/bioapi.py +++ b/bio-api/bioapi.py @@ -672,7 +672,7 @@ def create_app(): To contribute: [OmicsDatascience](https://github.com/omics-datascience/BioAPI)""" } - swagger = Swagger(flask_app, config=swagger_config) + Swagger(flask_app, config=swagger_config) # Endpoints @flask_app.route("/")