From 7153a9539950854fa35c028e822d222509aef48e Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jan 2022 13:02:39 -0500 Subject: [PATCH 01/41] Start queries web app --- .../apps/query_web_app/__init__.py | 40 +++++++++++++++++++ .../apps/query_web_app/__main__.py | 8 ++++ 2 files changed, 48 insertions(+) create mode 100644 src/indra_cogex/apps/query_web_app/__init__.py create mode 100644 src/indra_cogex/apps/query_web_app/__main__.py diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py new file mode 100644 index 000000000..065170a86 --- /dev/null +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +"""An app wrapping the query module of indra_cogex.""" +import logging + +import flask +from flask import request, abort, Response, jsonify +from more_click import make_web_command + +from indra_cogex.client.neo4j_client import Neo4jClient +from indra_cogex.client.queries import * + +app = flask.Flask(__name__) + +client = Neo4jClient() + + +logger = logging.getLogger(__name__) + + +@app.route('/get_genes_in_tissue', methods=['POST']) +def genes_in_tissue(): + """Get genes for a disease.""" + if request.json is None: + abort(Response('Missing application/json header.', 415)) + + disease_name = request.json.get('disease_name') + if disease_name is None: + abort(Response("Parameter 'disease_name' not provided", 415)) + logger.info('Getting genes for disease %s' % disease_name) + genes = get_genes_in_tissue(client, disease_name) + logger.info('Found %d genes' % len(genes)) + return jsonify([g.to_json() for g in genes]) + + +cli = make_web_command(app=app) + + +if __name__ == '__main__': + cli() diff --git a/src/indra_cogex/apps/query_web_app/__main__.py b/src/indra_cogex/apps/query_web_app/__main__.py new file mode 100644 index 000000000..018f6528c --- /dev/null +++ b/src/indra_cogex/apps/query_web_app/__main__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +"""Run the query web app with ``python -m indra_cogex.apps.query_web_app``.""" + +from . import cli + +if __name__ == '__main__': + cli() From faddbb2f8b7ee38bdf59c4afab2a9a1459822042 Mon Sep 17 00:00:00 2001 From: kkaris Date: Thu, 13 Jan 2022 13:03:07 -0500 Subject: [PATCH 02/41] WIP Hack to fix json serialization --- src/indra_cogex/representation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/indra_cogex/representation.py b/src/indra_cogex/representation.py index 0a6c65bfe..2293a58c4 100644 --- a/src/indra_cogex/representation.py +++ b/src/indra_cogex/representation.py @@ -104,7 +104,8 @@ def to_json(self) -> NodeJson: data = {k: v for k, v in self.data.items()} data["db_ns"] = self.db_ns data["db_id"] = self.db_id - return {"labels": self.labels, "data": data} + # Fixme: how to properly serialize labels? + return {"labels": [lb for lb in self.labels], "data": data} def _get_data_str(self) -> str: pieces = ["id:'%s:%s'" % (self.db_ns, self.db_id)] From b6a9f2b2933572b79d28e84d0c755a2594e86577 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 14 Jan 2022 09:50:59 -0500 Subject: [PATCH 03/41] Add one POST endpoint per query function --- .../apps/query_web_app/__init__.py | 363 +++++++++++++++++- 1 file changed, 357 insertions(+), 6 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 065170a86..24ad6caf8 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -18,23 +18,374 @@ logger = logging.getLogger(__name__) -@app.route('/get_genes_in_tissue', methods=['POST']) +@app.route("/get_genes_in_tissue", methods=["POST"]) def genes_in_tissue(): """Get genes for a disease.""" if request.json is None: - abort(Response('Missing application/json header.', 415)) + abort(Response("Missing application/json header.", 415)) - disease_name = request.json.get('disease_name') + disease_name = request.json.get("disease_name") if disease_name is None: abort(Response("Parameter 'disease_name' not provided", 415)) - logger.info('Getting genes for disease %s' % disease_name) genes = get_genes_in_tissue(client, disease_name) - logger.info('Found %d genes' % len(genes)) return jsonify([g.to_json() for g in genes]) +@app.route("/get_tissues_for_gene", methods=["POST"]) +def tissues_for_gene(): + """Get tissues for a gene.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + tissues = get_tissues_for_gene(client, gene_name) + return jsonify([t.to_json() for t in tissues]) + + +@app.route("/is_gene_in_tissue", methods=["POST"]) +def gene_in_tissue(): + """Check if a gene is in a tissue.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + tissue_name = request.json.get("tissue_name") + if tissue_name is None: + abort(Response("Parameter 'tissue_name' not provided", 415)) + is_in = is_gene_in_tissue(client, gene_name, tissue_name) + return jsonify({"gene_in_tissue": is_in}) + + +@app.route("/get_go_terms_for_gene", methods=["POST"]) +def go_terms_for_gene(): + """Get GO terms for a gene.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + go_terms = get_go_terms_for_gene(client, gene_name) + return jsonify([t.to_json() for t in go_terms]) + + +@app.route("/get_genes_for_go_term", methods=["POST"]) +def genes_for_go_term(): + """Get genes for a GO term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + go_term_name = request.json.get("go_term_name") + if go_term_name is None: + abort(Response("Parameter 'go_term_name' not provided", 415)) + genes = get_genes_for_go_term(client, go_term_name) + return jsonify([g.to_json() for g in genes]) + + +@app.route("/is_go_term_for_gene", methods=["POST"]) +def go_term_for_gene(): + """Check if a GO term is for a gene.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + go_term_name = request.json.get("go_term_name") + if go_term_name is None: + abort(Response("Parameter 'go_term_name' not provided", 415)) + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + is_in = is_go_term_for_gene(client, go_term_name, gene_name) + return jsonify({"go_term_for_gene": is_in}) + + +@app.route("/get_trials_for_drug", methods=["POST"]) +def trials_for_drug(): + """Get trials for a drug.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + drug_name = request.json.get("drug_name") + if drug_name is None: + abort(Response("Parameter 'drug_name' not provided", 415)) + trials = get_trials_for_drug(client, drug_name) + return jsonify([t.to_json() for t in trials]) + + +@app.route("/get_trials_for_disease", methods=["POST"]) +def trials_for_disease(): + """Get trials for a disease.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + disease_name = request.json.get("disease_name") + if disease_name is None: + abort(Response("Parameter 'disease_name' not provided", 415)) + trials = get_trials_for_disease(client, disease_name) + return jsonify([t.to_json() for t in trials]) + + +@app.route("/get_drugs_for_trial", methods=["POST"]) +def drugs_for_trial(): + """Get drugs for a trial.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + trial_name = request.json.get("trial_name") + if trial_name is None: + abort(Response("Parameter 'trial_name' not provided", 415)) + drugs = get_drugs_for_trial(client, trial_name) + return jsonify([d.to_json() for d in drugs]) + + +@app.route("/get_diseases_for_trial", methods=["POST"]) +def diseases_for_trial(): + """Get diseases for a trial.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + trial_name = request.json.get("trial_name") + if trial_name is None: + abort(Response("Parameter 'trial_name' not provided", 415)) + diseases = get_diseases_for_trial(client, trial_name) + return jsonify([d.to_json() for d in diseases]) + + +@app.route("/get_pathways_for_gene", methods=["POST"]) +def pathways_for_gene(): + """Get pathways for a gene.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + pathways = get_pathways_for_gene(client, gene_name) + return jsonify([p.to_json() for p in pathways]) + + +@app.route("/get_genes_for_pathway", methods=["POST"]) +def genes_for_pathway(): + """Get genes for a pathway.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + pathway_name = request.json.get("pathway_name") + if pathway_name is None: + abort(Response("Parameter 'pathway_name' not provided", 415)) + genes = get_genes_for_pathway(client, pathway_name) + return jsonify([g.to_json() for g in genes]) + + +@app.route("/is_gene_in_pathway", methods=["POST"]) +def gene_in_pathway(): + """Check if a gene is in a pathway.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene_name = request.json.get("gene_name") + if gene_name is None: + abort(Response("Parameter 'gene_name' not provided", 415)) + pathway_name = request.json.get("pathway_name") + if pathway_name is None: + abort(Response("Parameter 'pathway_name' not provided", 415)) + is_in = is_gene_in_pathway(client, gene_name, pathway_name) + return jsonify({"gene_in_pathway": is_in}) + + +@app.route("/get_side_effects_for_drug", methods=["POST"]) +def side_effects_for_drug(): + """Get side effects for a drug.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + drug_name = request.json.get("drug_name") + if drug_name is None: + abort(Response("Parameter 'drug_name' not provided", 415)) + side_effects = get_side_effects_for_drug(client, drug_name) + return jsonify([s.to_json() for s in side_effects]) + + +@app.route("/get_drugs_for_side_effect", methods=["POST"]) +def drugs_for_side_effect(): + """Get drugs for a side effect.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + side_effect_name = request.json.get("side_effect_name") + if side_effect_name is None: + abort(Response("Parameter 'side_effect_name' not provided", 415)) + drugs = get_drugs_for_side_effect(client, side_effect_name) + return jsonify([d.to_json() for d in drugs]) + + +@app.route("/is_side_effect_for_drug", methods=["POST"]) +def side_effect_for_drug(): + """Check if a side effect is for a drug.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + side_effect_name = request.json.get("side_effect_name") + if side_effect_name is None: + abort(Response("Parameter 'side_effect_name' not provided", 415)) + drug_name = request.json.get("drug_name") + if drug_name is None: + abort(Response("Parameter 'drug_name' not provided", 415)) + is_in = is_side_effect_for_drug(client, side_effect_name, drug_name) + return jsonify({"side_effect_for_drug": is_in}) + + +@app.route("/get_ontology_child_terms", methods=["POST"]) +def ontology_child_terms(): + """Get child terms of a term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + term_name = request.json.get("term_name") + if term_name is None: + abort(Response("Parameter 'term_name' not provided", 415)) + child_terms = get_ontology_child_terms(client, term_name) + return jsonify([t.to_json() for t in child_terms]) + + +@app.route("/get_ontology_parent_terms", methods=["POST"]) +def ontology_parent_terms(): + """Get parent terms of a term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + term_name = request.json.get("term_name") + if term_name is None: + abort(Response("Parameter 'term_name' not provided", 415)) + parent_terms = get_ontology_parent_terms(client, term_name) + return jsonify([t.to_json() for t in parent_terms]) + + +@app.route("/isa_or_partof", methods=["POST"]) +def isa_or_partof(): + """Check if a term is a part of another term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + term_name = request.json.get("term_name") + if term_name is None: + abort(Response("Parameter 'term_name' not provided", 415)) + parent_term_name = request.json.get("parent_term_name") + if parent_term_name is None: + abort(Response("Parameter 'parent_term_name' not provided", 415)) + is_isa = isa_or_partof(client, term_name, parent_term_name) + return jsonify({"isa_or_partof": is_isa}) + + +@app.route("/get_pmids_for_mesh", methods=["POST"]) +def pmids_for_mesh(): + """Get pmids for a mesh term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + mesh_term_name = request.json.get("mesh_term_name") + if mesh_term_name is None: + abort(Response("Parameter 'mesh_term_name' not provided", 415)) + pmids = get_pmids_for_mesh(client, mesh_term_name) + return jsonify([p.to_json() for p in pmids]) + + +@app.route("/get_mesh_ids_for_pmid", methods=["POST"]) +def mesh_ids_for_pmid(): + """Get mesh ids for a pmid.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + pmid = request.json.get("pmid") + if pmid is None: + abort(Response("Parameter 'pmid' not provided", 415)) + mesh_ids = get_mesh_ids_for_pmid(client, pmid) + return jsonify([m.to_json() for m in mesh_ids]) + + +@app.route("/get_evidences_for_mesh", methods=["POST"]) +def evidences_for_mesh(): + """Get evidences for a mesh term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + mesh_term_name = request.json.get("mesh_term_name") + if mesh_term_name is None: + abort(Response("Parameter 'mesh_term_name' not provided", 415)) + evidences = get_evidences_for_mesh(client, mesh_term_name) + return jsonify({h: [e.to_json() for e in el] for h, el in evidences.items()}) + + +@app.route("/get_evidences_for_stmt_hash", methods=["POST"]) +def evidences_for_stmt_hash(): + """Get evidences for a statement hash.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + stmt_hash = request.json.get("stmt_hash") + if stmt_hash is None: + abort(Response("Parameter 'stmt_hash' not provided", 415)) + evidences = get_evidences_for_stmt_hash(client, stmt_hash) + return jsonify([e.to_json() for e in evidences]) + + +@app.route("/get_evidences_for_stmt_hashes", methods=["POST"]) +def evidences_for_stmt_hashes(): + """Get evidences for a list of statement hashes.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + stmt_hashes = request.json.get("stmt_hashes") + if stmt_hashes is None: + abort(Response("Parameter 'stmt_hashes' not provided", 415)) + evidences = get_evidences_for_stmt_hashes(client, stmt_hashes) + return jsonify({h: [e.to_json() for e in el] for h, el in evidences.items()}) + + +@app.route("/get_stmts_for_pmid", methods=["POST"]) +def stmts_for_pmid(): + """Get statements for a pmid.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + pmid = request.json.get("pmid") + if pmid is None: + abort(Response("Parameter 'pmid' not provided", 415)) + stmts = get_stmts_for_pmid(client, pmid) + return jsonify([s.to_json() for s in stmts]) + + +@app.route("/get_stmts_for_mesh", methods=["POST"]) +def stmts_for_mesh(): + """Get statements for a mesh term.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + mesh_term_name = request.json.get("mesh_term_name") + if mesh_term_name is None: + abort(Response("Parameter 'mesh_term_name' not provided", 415)) + stmts = get_stmts_for_mesh(client, mesh_term_name) + return jsonify([s.to_json() for s in stmts]) + + +@app.route("/get_stmts_for_stmt_hashes", methods=["POST"]) +def stmts_for_stmt_hashes(): + """Get statements for a list of statement hashes.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + stmt_hashes = request.json.get("stmt_hashes") + if stmt_hashes is None: + abort(Response("Parameter 'stmt_hashes' not provided", 415)) + stmts = get_stmts_for_stmt_hashes(client, stmt_hashes) + return jsonify([s.to_json() for s in stmts]) + + cli = make_web_command(app=app) -if __name__ == '__main__': +if __name__ == "__main__": cli() From 6e140f5b6e95eee6f9238e27c87a2757acab9d88 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 14 Jan 2022 10:04:17 -0500 Subject: [PATCH 04/41] Use better variable name --- src/indra_cogex/apps/query_web_app/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 24ad6caf8..520f33537 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -315,8 +315,8 @@ def evidences_for_mesh(): mesh_term_name = request.json.get("mesh_term_name") if mesh_term_name is None: abort(Response("Parameter 'mesh_term_name' not provided", 415)) - evidences = get_evidences_for_mesh(client, mesh_term_name) - return jsonify({h: [e.to_json() for e in el] for h, el in evidences.items()}) + evidence_dict = get_evidences_for_mesh(client, mesh_term_name) + return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) @app.route("/get_evidences_for_stmt_hash", methods=["POST"]) @@ -341,8 +341,8 @@ def evidences_for_stmt_hashes(): stmt_hashes = request.json.get("stmt_hashes") if stmt_hashes is None: abort(Response("Parameter 'stmt_hashes' not provided", 415)) - evidences = get_evidences_for_stmt_hashes(client, stmt_hashes) - return jsonify({h: [e.to_json() for e in el] for h, el in evidences.items()}) + evidence_dict = get_evidences_for_stmt_hashes(client, stmt_hashes) + return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) @app.route("/get_stmts_for_pmid", methods=["POST"]) From 22c6ab803ce9caeda50beac2b9ffe7be9c15277d Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 14 Jan 2022 10:29:52 -0500 Subject: [PATCH 05/41] Add preprocessing helper --- .../apps/query_web_app/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 520f33537..c3ea5a164 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -2,6 +2,7 @@ """An app wrapping the query module of indra_cogex.""" import logging +from typing import Tuple, Union import flask from flask import request, abort, Response, jsonify @@ -17,6 +18,24 @@ logger = logging.getLogger(__name__) +Tup = Tuple[str, str] +TupOfTups = Tuple[Tup, ...] + + +def _post_request_preproc(*keys) -> Union[TupOfTups, Tup]: + if request.json is None: + abort(Response("Missing application/json header.", 415)) + tups = [] + for key in keys: + if key not in request.json: + abort(Response("Parameter '%s' not provided" % key, 415)) + tups.append(tuple(request.json[key])) + + if len(tups) == 1: + return tups[0] + + return tuple(tups) + @app.route("/get_genes_in_tissue", methods=["POST"]) def genes_in_tissue(): From 1e3013be0ea89682d7567ebba1f5bfc9ffba031c Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 14 Jan 2022 10:30:29 -0500 Subject: [PATCH 06/41] Add preprocessing helper to first query --- src/indra_cogex/apps/query_web_app/__init__.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index c3ea5a164..310e006d2 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -40,13 +40,8 @@ def _post_request_preproc(*keys) -> Union[TupOfTups, Tup]: @app.route("/get_genes_in_tissue", methods=["POST"]) def genes_in_tissue(): """Get genes for a disease.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - disease_name = request.json.get("disease_name") - if disease_name is None: - abort(Response("Parameter 'disease_name' not provided", 415)) - genes = get_genes_in_tissue(client, disease_name) + tissue_name = _post_request_preproc("tissue_name") + genes = get_genes_in_tissue(client, tissue_name) return jsonify([g.to_json() for g in genes]) From 4ebda5e890f38242a3bfc279c07df69c023c4bb8 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 7 Feb 2022 15:39:19 -0500 Subject: [PATCH 07/41] Add endpoint for get_shared_pathways_for_genes --- src/indra_cogex/apps/query_web_app/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 310e006d2..31e73f93b 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -181,6 +181,19 @@ def pathways_for_gene(): return jsonify([p.to_json() for p in pathways]) +@app.route("/get_shared_pathways_for_genes", methods=["POST"]) +def shared_pathways_for_genes(): + """Get shared genes for a pathway.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + genes = request.json.get("genes") + if genes is None: + abort(Response("Parameter 'genes' not provided", 415)) + pathways = get_shared_pathways_for_genes(genes, client=client) + return jsonify([p.to_json() for p in pathways]) + + @app.route("/get_genes_for_pathway", methods=["POST"]) def genes_for_pathway(): """Get genes for a pathway.""" From 689479dd715e1ff5476137c204e8a963a2b17d87 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 7 Feb 2022 15:48:38 -0500 Subject: [PATCH 08/41] Add four more endpoints --- .../apps/query_web_app/__init__.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 31e73f93b..735127f13 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -411,6 +411,62 @@ def stmts_for_stmt_hashes(): return jsonify([s.to_json() for s in stmts]) +@app.route("/is_gene_mutated", methods=["POST"]) +def gene_mutated(): + """Check if a gene is mutated in a cell line""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + gene = request.json.get("gene") + if gene is None: + abort(Response("Parameter 'gene' not provided", 415)) + cell_line = request.json.get("cell_line") + if cell_line is None: + abort(Response("Parameter 'cell_line' not provided", 415)) + return jsonify({'is_gene_mutated': is_gene_mutated(gene, cell_line, client=client)}) + + +@app.route("/get_drugs_for_target", methods=["POST"]) +def drugs_for_target(): + """Get drugs for a target.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + target = request.json.get("target") + if target is None: + abort(Response("Parameter 'target' not provided", 415)) + drugs = get_drugs_for_target(target, client=client) + return jsonify([d.to_json() for d in drugs]) + + +@app.route("/get_targets_for_drug", methods=["POST"]) +def targets_for_drug(): + """Get targets for a drug.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + drug = request.json.get("drug") + if drug is None: + abort(Response("Parameter 'drug' not provided", 415)) + targets = get_targets_for_drug(drug, client=client) + return jsonify([t.to_json() for t in targets]) + + +@app.route("/is_drug_target", methods=["POST"]) +def drug_target(): + """Check if the drug targets the given protein.""" + if request.json is None: + abort(Response("Missing application/json header.", 415)) + + drug = request.json.get("drug") + if drug is None: + abort(Response("Parameter 'drug' not provided", 415)) + target = request.json.get("target") + if target is None: + abort(Response("Parameter 'target' not provided", 415)) + return jsonify({'is_drug_target': is_drug_target(drug, target, client=client)}) + + cli = make_web_command(app=app) From 6058239204c3af7eee1f575a676c6fc9e771ab7c Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 7 Feb 2022 16:30:56 -0500 Subject: [PATCH 09/41] Use kwargs, use helper checker --- .../apps/query_web_app/__init__.py | 347 +++++------------- 1 file changed, 102 insertions(+), 245 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 735127f13..5e180aafd 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -40,431 +40,288 @@ def _post_request_preproc(*keys) -> Union[TupOfTups, Tup]: @app.route("/get_genes_in_tissue", methods=["POST"]) def genes_in_tissue(): """Get genes for a disease.""" - tissue_name = _post_request_preproc("tissue_name") - genes = get_genes_in_tissue(client, tissue_name) + tissue = _post_request_preproc("tissue") + genes = get_genes_in_tissue(tissue=tissue, client=client) return jsonify([g.to_json() for g in genes]) @app.route("/get_tissues_for_gene", methods=["POST"]) def tissues_for_gene(): """Get tissues for a gene.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - tissues = get_tissues_for_gene(client, gene_name) + gene = _post_request_preproc("gene") + tissues = get_tissues_for_gene(gene=gene, client=client) return jsonify([t.to_json() for t in tissues]) @app.route("/is_gene_in_tissue", methods=["POST"]) def gene_in_tissue(): """Check if a gene is in a tissue.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - tissue_name = request.json.get("tissue_name") - if tissue_name is None: - abort(Response("Parameter 'tissue_name' not provided", 415)) - is_in = is_gene_in_tissue(client, gene_name, tissue_name) - return jsonify({"gene_in_tissue": is_in}) + gene, tissue = _post_request_preproc("gene", "tissue") + return jsonify( + {"gene_in_tissue": is_gene_in_tissue(gene=gene, tissue=tissue, client=client)} + ) @app.route("/get_go_terms_for_gene", methods=["POST"]) def go_terms_for_gene(): """Get GO terms for a gene.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - go_terms = get_go_terms_for_gene(client, gene_name) + gene = _post_request_preproc("gene") + go_terms = get_go_terms_for_gene(gene=gene, client=client) return jsonify([t.to_json() for t in go_terms]) @app.route("/get_genes_for_go_term", methods=["POST"]) def genes_for_go_term(): """Get genes for a GO term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - go_term_name = request.json.get("go_term_name") - if go_term_name is None: - abort(Response("Parameter 'go_term_name' not provided", 415)) - genes = get_genes_for_go_term(client, go_term_name) + go_term = _post_request_preproc("go_term") + genes = get_genes_for_go_term(go_term=go_term, client=client) return jsonify([g.to_json() for g in genes]) @app.route("/is_go_term_for_gene", methods=["POST"]) def go_term_for_gene(): """Check if a GO term is for a gene.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - go_term_name = request.json.get("go_term_name") - if go_term_name is None: - abort(Response("Parameter 'go_term_name' not provided", 415)) - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - is_in = is_go_term_for_gene(client, go_term_name, gene_name) - return jsonify({"go_term_for_gene": is_in}) + gene, go_term = _post_request_preproc("gene", "go_term") + return jsonify( + { + "go_term_for_gene": is_go_term_for_gene( + gene=gene, go_term=go_term, client=client + ) + } + ) @app.route("/get_trials_for_drug", methods=["POST"]) def trials_for_drug(): """Get trials for a drug.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - drug_name = request.json.get("drug_name") - if drug_name is None: - abort(Response("Parameter 'drug_name' not provided", 415)) - trials = get_trials_for_drug(client, drug_name) + drug = _post_request_preproc("drug") + trials = get_trials_for_drug(drug=drug, client=client) return jsonify([t.to_json() for t in trials]) @app.route("/get_trials_for_disease", methods=["POST"]) def trials_for_disease(): """Get trials for a disease.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - disease_name = request.json.get("disease_name") - if disease_name is None: - abort(Response("Parameter 'disease_name' not provided", 415)) - trials = get_trials_for_disease(client, disease_name) + disease = _post_request_preproc("disease") + trials = get_trials_for_disease(disease=disease, client=client) return jsonify([t.to_json() for t in trials]) @app.route("/get_drugs_for_trial", methods=["POST"]) def drugs_for_trial(): """Get drugs for a trial.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - trial_name = request.json.get("trial_name") - if trial_name is None: - abort(Response("Parameter 'trial_name' not provided", 415)) - drugs = get_drugs_for_trial(client, trial_name) + trial = _post_request_preproc("trial") + drugs = get_drugs_for_trial(trial=trial, client=client) return jsonify([d.to_json() for d in drugs]) @app.route("/get_diseases_for_trial", methods=["POST"]) def diseases_for_trial(): """Get diseases for a trial.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - trial_name = request.json.get("trial_name") - if trial_name is None: - abort(Response("Parameter 'trial_name' not provided", 415)) - diseases = get_diseases_for_trial(client, trial_name) + trial = _post_request_preproc("trial") + diseases = get_diseases_for_trial(trial=trial, client=client) return jsonify([d.to_json() for d in diseases]) @app.route("/get_pathways_for_gene", methods=["POST"]) def pathways_for_gene(): """Get pathways for a gene.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - pathways = get_pathways_for_gene(client, gene_name) + gene = _post_request_preproc("gene") + pathways = get_pathways_for_gene(gene=gene, client=client) return jsonify([p.to_json() for p in pathways]) @app.route("/get_shared_pathways_for_genes", methods=["POST"]) def shared_pathways_for_genes(): """Get shared genes for a pathway.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - genes = request.json.get("genes") - if genes is None: - abort(Response("Parameter 'genes' not provided", 415)) - pathways = get_shared_pathways_for_genes(genes, client=client) + genes = _post_request_preproc("genes") + if not isinstance(genes, list): + genes = [genes] + pathways = get_shared_pathways_for_genes(genes=genes, client=client) return jsonify([p.to_json() for p in pathways]) @app.route("/get_genes_for_pathway", methods=["POST"]) def genes_for_pathway(): """Get genes for a pathway.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - pathway_name = request.json.get("pathway_name") - if pathway_name is None: - abort(Response("Parameter 'pathway_name' not provided", 415)) - genes = get_genes_for_pathway(client, pathway_name) + pathway = _post_request_preproc("pathway") + genes = get_genes_for_pathway(pathway=pathway, client=client) return jsonify([g.to_json() for g in genes]) @app.route("/is_gene_in_pathway", methods=["POST"]) def gene_in_pathway(): """Check if a gene is in a pathway.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene_name = request.json.get("gene_name") - if gene_name is None: - abort(Response("Parameter 'gene_name' not provided", 415)) - pathway_name = request.json.get("pathway_name") - if pathway_name is None: - abort(Response("Parameter 'pathway_name' not provided", 415)) - is_in = is_gene_in_pathway(client, gene_name, pathway_name) - return jsonify({"gene_in_pathway": is_in}) + gene, pathway = _post_request_preproc("gene", "pathway") + return jsonify( + { + "gene_in_pathway": is_gene_in_pathway( + gene=gene, pathway=pathway, client=client + ) + } + ) @app.route("/get_side_effects_for_drug", methods=["POST"]) def side_effects_for_drug(): """Get side effects for a drug.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - drug_name = request.json.get("drug_name") - if drug_name is None: - abort(Response("Parameter 'drug_name' not provided", 415)) - side_effects = get_side_effects_for_drug(client, drug_name) + drug = _post_request_preproc("drug") + side_effects = get_side_effects_for_drug(drug=drug, client=client) return jsonify([s.to_json() for s in side_effects]) @app.route("/get_drugs_for_side_effect", methods=["POST"]) def drugs_for_side_effect(): """Get drugs for a side effect.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - side_effect_name = request.json.get("side_effect_name") - if side_effect_name is None: - abort(Response("Parameter 'side_effect_name' not provided", 415)) - drugs = get_drugs_for_side_effect(client, side_effect_name) + side_effect = _post_request_preproc("side_effect") + drugs = get_drugs_for_side_effect(side_effect=side_effect, client=client) return jsonify([d.to_json() for d in drugs]) @app.route("/is_side_effect_for_drug", methods=["POST"]) def side_effect_for_drug(): """Check if a side effect is for a drug.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - side_effect_name = request.json.get("side_effect_name") - if side_effect_name is None: - abort(Response("Parameter 'side_effect_name' not provided", 415)) - drug_name = request.json.get("drug_name") - if drug_name is None: - abort(Response("Parameter 'drug_name' not provided", 415)) - is_in = is_side_effect_for_drug(client, side_effect_name, drug_name) - return jsonify({"side_effect_for_drug": is_in}) + drug, side_effect = _post_request_preproc("drug", "side_effect") + return jsonify( + { + "side_effect_for_drug": is_side_effect_for_drug( + drug=drug, side_effect=side_effect, client=client + ) + } + ) @app.route("/get_ontology_child_terms", methods=["POST"]) def ontology_child_terms(): """Get child terms of a term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - term_name = request.json.get("term_name") - if term_name is None: - abort(Response("Parameter 'term_name' not provided", 415)) - child_terms = get_ontology_child_terms(client, term_name) + term = _post_request_preproc("term") + child_terms = get_ontology_child_terms(term=term, client=client) return jsonify([t.to_json() for t in child_terms]) @app.route("/get_ontology_parent_terms", methods=["POST"]) def ontology_parent_terms(): """Get parent terms of a term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - term_name = request.json.get("term_name") - if term_name is None: - abort(Response("Parameter 'term_name' not provided", 415)) - parent_terms = get_ontology_parent_terms(client, term_name) + term = _post_request_preproc("term") + parent_terms = get_ontology_parent_terms(term=term, client=client) return jsonify([t.to_json() for t in parent_terms]) @app.route("/isa_or_partof", methods=["POST"]) def isa_or_partof(): """Check if a term is a part of another term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - term_name = request.json.get("term_name") - if term_name is None: - abort(Response("Parameter 'term_name' not provided", 415)) - parent_term_name = request.json.get("parent_term_name") - if parent_term_name is None: - abort(Response("Parameter 'parent_term_name' not provided", 415)) - is_isa = isa_or_partof(client, term_name, parent_term_name) - return jsonify({"isa_or_partof": is_isa}) + term, parent_term = _post_request_preproc("term", "parent_term") + return jsonify( + { + "isa_or_partof": isa_or_partof( + term=term, parent_term=parent_term, client=client + ) + } + ) @app.route("/get_pmids_for_mesh", methods=["POST"]) def pmids_for_mesh(): """Get pmids for a mesh term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - mesh_term_name = request.json.get("mesh_term_name") - if mesh_term_name is None: - abort(Response("Parameter 'mesh_term_name' not provided", 415)) - pmids = get_pmids_for_mesh(client, mesh_term_name) + mesh_term = _post_request_preproc("mesh_term") + pmids = get_pmids_for_mesh(mesh_term=mesh_term, client=client) return jsonify([p.to_json() for p in pmids]) @app.route("/get_mesh_ids_for_pmid", methods=["POST"]) def mesh_ids_for_pmid(): """Get mesh ids for a pmid.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - pmid = request.json.get("pmid") - if pmid is None: - abort(Response("Parameter 'pmid' not provided", 415)) - mesh_ids = get_mesh_ids_for_pmid(client, pmid) + pmid_term = _post_request_preproc("pmid_term") + mesh_ids = get_mesh_ids_for_pmid(pmid_term=pmid_term, client=client) return jsonify([m.to_json() for m in mesh_ids]) @app.route("/get_evidences_for_mesh", methods=["POST"]) def evidences_for_mesh(): """Get evidences for a mesh term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - mesh_term_name = request.json.get("mesh_term_name") - if mesh_term_name is None: - abort(Response("Parameter 'mesh_term_name' not provided", 415)) - evidence_dict = get_evidences_for_mesh(client, mesh_term_name) + mesh_term = _post_request_preproc("mesh_term") + evidence_dict = get_evidences_for_mesh(mesh_term=mesh_term, client=client) return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) @app.route("/get_evidences_for_stmt_hash", methods=["POST"]) def evidences_for_stmt_hash(): """Get evidences for a statement hash.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - stmt_hash = request.json.get("stmt_hash") - if stmt_hash is None: - abort(Response("Parameter 'stmt_hash' not provided", 415)) - evidences = get_evidences_for_stmt_hash(client, stmt_hash) + stmt_hash = _post_request_preproc("stmt_hash") + evidences = get_evidences_for_stmt_hash(stmt_hash=stmt_hash, client=client) return jsonify([e.to_json() for e in evidences]) @app.route("/get_evidences_for_stmt_hashes", methods=["POST"]) def evidences_for_stmt_hashes(): """Get evidences for a list of statement hashes.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - stmt_hashes = request.json.get("stmt_hashes") - if stmt_hashes is None: - abort(Response("Parameter 'stmt_hashes' not provided", 415)) - evidence_dict = get_evidences_for_stmt_hashes(client, stmt_hashes) + stmt_hashes = _post_request_preproc("stmt_hashes") + evidence_dict = get_evidences_for_stmt_hashes( + stmt_hashes=stmt_hashes, client=client + ) return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) @app.route("/get_stmts_for_pmid", methods=["POST"]) def stmts_for_pmid(): """Get statements for a pmid.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - pmid = request.json.get("pmid") - if pmid is None: - abort(Response("Parameter 'pmid' not provided", 415)) - stmts = get_stmts_for_pmid(client, pmid) + pmid_term = _post_request_preproc("pmid_term") + stmts = get_stmts_for_pmid(pmid_term=pmid_term, client=client) return jsonify([s.to_json() for s in stmts]) @app.route("/get_stmts_for_mesh", methods=["POST"]) def stmts_for_mesh(): """Get statements for a mesh term.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - mesh_term_name = request.json.get("mesh_term_name") - if mesh_term_name is None: - abort(Response("Parameter 'mesh_term_name' not provided", 415)) - stmts = get_stmts_for_mesh(client, mesh_term_name) + mesh_term = _post_request_preproc("mesh_term") + stmts = get_stmts_for_mesh(mesh_term=mesh_term, client=client) return jsonify([s.to_json() for s in stmts]) @app.route("/get_stmts_for_stmt_hashes", methods=["POST"]) def stmts_for_stmt_hashes(): """Get statements for a list of statement hashes.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - stmt_hashes = request.json.get("stmt_hashes") - if stmt_hashes is None: - abort(Response("Parameter 'stmt_hashes' not provided", 415)) - stmts = get_stmts_for_stmt_hashes(client, stmt_hashes) + stmt_hashes = _post_request_preproc("stmt_hashes") + stmts = get_stmts_for_stmt_hashes(stmt_hashes=stmt_hashes, client=client) return jsonify([s.to_json() for s in stmts]) @app.route("/is_gene_mutated", methods=["POST"]) def gene_mutated(): """Check if a gene is mutated in a cell line""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - gene = request.json.get("gene") - if gene is None: - abort(Response("Parameter 'gene' not provided", 415)) - cell_line = request.json.get("cell_line") - if cell_line is None: - abort(Response("Parameter 'cell_line' not provided", 415)) - return jsonify({'is_gene_mutated': is_gene_mutated(gene, cell_line, client=client)}) + gene, cell_line = _post_request_preproc("gene", "cell_line") + return jsonify( + { + "is_gene_mutated": is_gene_mutated( + gene=gene, cell_line=cell_line, client=client + ) + } + ) @app.route("/get_drugs_for_target", methods=["POST"]) def drugs_for_target(): """Get drugs for a target.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - target = request.json.get("target") - if target is None: - abort(Response("Parameter 'target' not provided", 415)) - drugs = get_drugs_for_target(target, client=client) + target = _post_request_preproc("target") + drugs = get_drugs_for_target(target=target, client=client) return jsonify([d.to_json() for d in drugs]) @app.route("/get_targets_for_drug", methods=["POST"]) def targets_for_drug(): """Get targets for a drug.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - drug = request.json.get("drug") - if drug is None: - abort(Response("Parameter 'drug' not provided", 415)) - targets = get_targets_for_drug(drug, client=client) + drug = _post_request_preproc("drug") + targets = get_targets_for_drug(drug=drug, client=client) return jsonify([t.to_json() for t in targets]) @app.route("/is_drug_target", methods=["POST"]) def drug_target(): """Check if the drug targets the given protein.""" - if request.json is None: - abort(Response("Missing application/json header.", 415)) - - drug = request.json.get("drug") - if drug is None: - abort(Response("Parameter 'drug' not provided", 415)) - target = request.json.get("target") - if target is None: - abort(Response("Parameter 'target' not provided", 415)) - return jsonify({'is_drug_target': is_drug_target(drug, target, client=client)}) + drug, target = _post_request_preproc("drug", "target") + return jsonify( + {"is_drug_target": is_drug_target(drug=drug, target=target, client=client)} + ) cli = make_web_command(app=app) From 5197648013f83dbe77ad1577d541cde205dbfe9e Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 7 Feb 2022 16:31:17 -0500 Subject: [PATCH 10/41] Add str, int as return types from checker --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 5e180aafd..70df1440c 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -22,7 +22,7 @@ TupOfTups = Tuple[Tup, ...] -def _post_request_preproc(*keys) -> Union[TupOfTups, Tup]: +def _post_request_preproc(*keys) -> Union[TupOfTups, Tup, str, int]: if request.json is None: abort(Response("Missing application/json header.", 415)) tups = [] From f8f127b894c18a0956e003220c0d8d4abfe62675 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 23 Feb 2022 13:04:25 -0500 Subject: [PATCH 11/41] Type annotate get_go_terms_for_gene --- src/indra_cogex/client/queries.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 32fb15e5b..598982c20 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -140,7 +140,7 @@ def is_gene_in_tissue( @autoclient() def get_go_terms_for_gene( - gene: Tuple[str, str], include_indirect=False, *, client: Neo4jClient + gene: Tuple[str, str], include_indirect: bool = False, *, client: Neo4jClient ) -> Iterable[Node]: """Return the GO terms for the given gene. @@ -150,6 +150,8 @@ def get_go_terms_for_gene( The Neo4j client. gene : The gene to query. + include_indirect : + If True, also return indirect GO terms. Returns ------- From f82134400a588a3442617429a31dc41fb092b366 Mon Sep 17 00:00:00 2001 From: kkaris Date: Wed, 23 Feb 2022 14:51:01 -0500 Subject: [PATCH 12/41] Add to dosctrings of three functions --- src/indra_cogex/client/queries.py | 47 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 598982c20..b4ad840e1 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -1091,7 +1091,20 @@ def is_gene_mutated( def get_drugs_for_target( target: Tuple[str, str], *, client: Neo4jClient ) -> Iterable[Agent]: - """Return the drugs targeting the given protein.""" + """Return the drugs targeting the given protein. + + Parameters + ---------- + client : + The Neo4j client. + target : + The target to query. + + Returns + ------- + : + The drugs targeting the given protein. + """ rels = client.get_source_relations( target, "indra_rel", source_type="BioEntity", target_type="BioEntity" ) @@ -1125,7 +1138,20 @@ def get_drugs_for_targets( def get_targets_for_drug( drug: Tuple[str, str], *, client: Neo4jClient ) -> Iterable[Agent]: - """Return the proteins targeted by the given drug.""" + """Return the proteins targeted by the given drug. + + Parameters + ---------- + client : + The Neo4j client. + drug : + The drug to query. + + Returns + ------- + : + The proteins targeted by the given drug. + """ rels = client.get_target_relations( drug, "indra_rel", source_type="BioEntity", target_type="BioEntity" ) @@ -1159,7 +1185,22 @@ def get_targets_for_drugs( def is_drug_target( drug: Tuple[str, str], target: Tuple[str, str], *, client: Neo4jClient ) -> bool: - """Return True if the drug targets the given protein.""" + """Return True if the drug targets the given protein. + + Parameters + ---------- + client : + The Neo4j client. + drug : + The drug to query. + target : + The target to query. + + Returns + ------- + : + True if the drug targets the given protein. + """ rels = client.get_relations( drug, target, "indra_rel", source_type="BioEntity", target_type="BioEntity" ) From de5c4cd558dbcdbbfe5d6b5be4af2dbf95ff76e8 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 14 Jan 2022 09:26:32 -0500 Subject: [PATCH 13/41] Prepare for flask-restx-models --- .../apps/query_web_app/__init__.py | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 70df1440c..dacb9336c 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -2,16 +2,32 @@ """An app wrapping the query module of indra_cogex.""" import logging -from typing import Tuple, Union +from typing import Dict, Callable, Tuple, Union import flask -from flask import request, abort, Response, jsonify +from docstring_parser import parse +from flask import request, Response, jsonify +from flask_restx import Api, Resource, fields, abort from more_click import make_web_command from indra_cogex.client.neo4j_client import Neo4jClient from indra_cogex.client.queries import * +from indra_cogex.client import queries + +from .query_models import query_model_map app = flask.Flask(__name__) +api = Api( + app, + title='INDRA CoGEx Query API', + description='REST API for INDRA CoGEx queries' +) + +query_ns = api.namespace('CoGEx Queries', 'Queries for INDRA CoGEx', path='/api/') +query_model = api.model('Query', { + "client": "Neo4jClient", + "term": fields.List[fields.String, fields.String] +}) client = Neo4jClient() @@ -78,6 +94,68 @@ def genes_for_go_term(): return jsonify([g.to_json() for g in genes]) +class QueryResource(Resource): + """A resource for a query.""" + func_name = NotImplemented + + def __init__(self, query_func: Callable): + """Initialize the resource.""" + super().__init__() + self.query_func = query_func + + def post(self): + """Get a query.""" + args = request.json + logger.info('Getting query %s' % self.query_func.__name__) + result = self.query_func(client, args) + logger.info('Found %d results' % len(result)) + return jsonify(result) + + +def _name_func_map() -> Dict[str, Callable]: + func_map = {} + for func_name in queries.__all__: + func = getattr(queries, func_name) + func_map[func_name] = func + return func_map + + +query_func_map = _name_func_map() + + +def get_docstring(fun: Callable) -> Tuple[str, str]: + parsed_doc = parse(fun.__doc__) + + long = parsed_doc.short_description + '\n\n' + + if parsed_doc.long_description: + long += (parsed_doc.long_description + '\n\n') + + long += 'Parameters\n----------\n' + for param in parsed_doc.params: + long += param.arg_name + ': ' + param.description + '\n' + long += '\n' + long += ('Returns\n-------\n') + long += parsed_doc.returns.description + + return parsed_doc.short_description, parsed_doc.long_description + + +# Create resource for each query function +for func_name, func in query_func_map.items(): + short_doc, doc = get_docstring(func) + + @query_ns.expect(query_model) + @query_ns.route('/' + func_name, + doc={'summary': short_doc}) + class NewQueryResource(QueryResource): + func_name = func_name + + def post(self): + return super().post() + + post.__doc__ = doc + @app.route("/is_go_term_for_gene", methods=["POST"]) def go_term_for_gene(): """Check if a GO term is for a gene.""" From dc9ce4016188b64c90939066e8a1acbdd8444568 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 11:13:22 -0500 Subject: [PATCH 14/41] WIP Make for loop modeling work --- .../apps/query_web_app/__init__.py | 414 +++--------------- 1 file changed, 59 insertions(+), 355 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index dacb9336c..b2287336b 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -2,7 +2,8 @@ """An app wrapping the query module of indra_cogex.""" import logging -from typing import Dict, Callable, Tuple, Union +from inspect import isfunction, signature +from typing import Callable, Tuple, Union import flask from docstring_parser import parse @@ -11,24 +12,15 @@ from more_click import make_web_command from indra_cogex.client.neo4j_client import Neo4jClient -from indra_cogex.client.queries import * from indra_cogex.client import queries -from .query_models import query_model_map app = flask.Flask(__name__) api = Api( - app, - title='INDRA CoGEx Query API', - description='REST API for INDRA CoGEx queries' + app, title="INDRA CoGEx Query API", description="REST API for INDRA CoGEx queries" ) -query_ns = api.namespace('CoGEx Queries', 'Queries for INDRA CoGEx', path='/api/') -query_model = api.model('Query', { - "client": "Neo4jClient", - "term": fields.List[fields.String, fields.String] -}) - +query_ns = api.namespace("CoGEx Queries", "Queries for INDRA CoGEx", path="/api/") client = Neo4jClient() @@ -38,368 +30,80 @@ TupOfTups = Tuple[Tup, ...] -def _post_request_preproc(*keys) -> Union[TupOfTups, Tup, str, int]: - if request.json is None: - abort(Response("Missing application/json header.", 415)) - tups = [] - for key in keys: - if key not in request.json: - abort(Response("Parameter '%s' not provided" % key, 415)) - tups.append(tuple(request.json[key])) - - if len(tups) == 1: - return tups[0] - - return tuple(tups) - - -@app.route("/get_genes_in_tissue", methods=["POST"]) -def genes_in_tissue(): - """Get genes for a disease.""" - tissue = _post_request_preproc("tissue") - genes = get_genes_in_tissue(tissue=tissue, client=client) - return jsonify([g.to_json() for g in genes]) - - -@app.route("/get_tissues_for_gene", methods=["POST"]) -def tissues_for_gene(): - """Get tissues for a gene.""" - gene = _post_request_preproc("gene") - tissues = get_tissues_for_gene(gene=gene, client=client) - return jsonify([t.to_json() for t in tissues]) - - -@app.route("/is_gene_in_tissue", methods=["POST"]) -def gene_in_tissue(): - """Check if a gene is in a tissue.""" - gene, tissue = _post_request_preproc("gene", "tissue") - return jsonify( - {"gene_in_tissue": is_gene_in_tissue(gene=gene, tissue=tissue, client=client)} - ) - - -@app.route("/get_go_terms_for_gene", methods=["POST"]) -def go_terms_for_gene(): - """Get GO terms for a gene.""" - gene = _post_request_preproc("gene") - go_terms = get_go_terms_for_gene(gene=gene, client=client) - return jsonify([t.to_json() for t in go_terms]) - - -@app.route("/get_genes_for_go_term", methods=["POST"]) -def genes_for_go_term(): - """Get genes for a GO term.""" - go_term = _post_request_preproc("go_term") - genes = get_genes_for_go_term(go_term=go_term, client=client) - return jsonify([g.to_json() for g in genes]) - - -class QueryResource(Resource): - """A resource for a query.""" - func_name = NotImplemented - - def __init__(self, query_func: Callable): - """Initialize the resource.""" - super().__init__() - self.query_func = query_func - - def post(self): - """Get a query.""" - args = request.json - logger.info('Getting query %s' % self.query_func.__name__) - result = self.query_func(client, args) - logger.info('Found %d results' % len(result)) - return jsonify(result) - - -def _name_func_map() -> Dict[str, Callable]: - func_map = {} - for func_name in queries.__all__: - func = getattr(queries, func_name) - func_map[func_name] = func - return func_map - - -query_func_map = _name_func_map() - - def get_docstring(fun: Callable) -> Tuple[str, str]: parsed_doc = parse(fun.__doc__) - long = parsed_doc.short_description + '\n\n' + long = parsed_doc.short_description + "\n\n" if parsed_doc.long_description: - long += (parsed_doc.long_description + '\n\n') + long += parsed_doc.long_description + "\n\n" - long += 'Parameters\n----------\n' + long += "Parameters\n----------\n" for param in parsed_doc.params: - long += param.arg_name + ': ' + param.description + '\n' - long += '\n' - long += ('Returns\n-------\n') + long += param.arg_name + ": " + param.description + "\n" + long += "\n" + long += "Returns\n-------\n" long += parsed_doc.returns.description return parsed_doc.short_description, parsed_doc.long_description +func_mapping = {fname: getattr(queries, fname) for fname in queries.__all__} + + # Create resource for each query function -for func_name, func in query_func_map.items(): +for func_name in queries.__all__: + if not isfunction(getattr(queries, func_name)) or func_name == "get_schema_graph": + continue + + func = getattr(queries, func_name) + func_sig = signature(func) + client_param = func_sig.parameters.get("client") + if client_param is None: + continue + short_doc, doc = get_docstring(func) + param_names = list(func_sig.parameters.keys()) + param_names.remove("client") + + name_title = func_name.replace("_", " ").title() + + # Create query model, separate between one and two parameter expectations + if len(func_sig.parameters) == 2: + # Get the parameters name for the other parameter that is not 'client' + query_model = api.model( + f"{func_name}_endpoint", {param_names[0]: fields.List(fields.String)} + ) + elif len(func_sig.parameters) == 3: + + query_model = api.model( + f"{func_name}_endpoint", + { + param_names[0]: fields.List(fields.String), + param_names[1]: fields.List(fields.String), + }, + ) + else: + raise ValueError( + f"Query function {func_name} has an unexpected number of " + f"parameters ({len(func_sig.parameters)})" + ) + @query_ns.expect(query_model) - @query_ns.route('/' + func_name, - doc={'summary': short_doc}) - class NewQueryResource(QueryResource): + @query_ns.route(f"/{func_name}", doc={"summary": "This is the summary"}) + class QueryResource(Resource): + """A resource for a query.""" + func_name = func_name def post(self): - return super().post() - - post.__doc__ = doc - -@app.route("/is_go_term_for_gene", methods=["POST"]) -def go_term_for_gene(): - """Check if a GO term is for a gene.""" - gene, go_term = _post_request_preproc("gene", "go_term") - return jsonify( - { - "go_term_for_gene": is_go_term_for_gene( - gene=gene, go_term=go_term, client=client - ) - } - ) - - -@app.route("/get_trials_for_drug", methods=["POST"]) -def trials_for_drug(): - """Get trials for a drug.""" - drug = _post_request_preproc("drug") - trials = get_trials_for_drug(drug=drug, client=client) - return jsonify([t.to_json() for t in trials]) - - -@app.route("/get_trials_for_disease", methods=["POST"]) -def trials_for_disease(): - """Get trials for a disease.""" - disease = _post_request_preproc("disease") - trials = get_trials_for_disease(disease=disease, client=client) - return jsonify([t.to_json() for t in trials]) - - -@app.route("/get_drugs_for_trial", methods=["POST"]) -def drugs_for_trial(): - """Get drugs for a trial.""" - trial = _post_request_preproc("trial") - drugs = get_drugs_for_trial(trial=trial, client=client) - return jsonify([d.to_json() for d in drugs]) - - -@app.route("/get_diseases_for_trial", methods=["POST"]) -def diseases_for_trial(): - """Get diseases for a trial.""" - trial = _post_request_preproc("trial") - diseases = get_diseases_for_trial(trial=trial, client=client) - return jsonify([d.to_json() for d in diseases]) - - -@app.route("/get_pathways_for_gene", methods=["POST"]) -def pathways_for_gene(): - """Get pathways for a gene.""" - gene = _post_request_preproc("gene") - pathways = get_pathways_for_gene(gene=gene, client=client) - return jsonify([p.to_json() for p in pathways]) - - -@app.route("/get_shared_pathways_for_genes", methods=["POST"]) -def shared_pathways_for_genes(): - """Get shared genes for a pathway.""" - genes = _post_request_preproc("genes") - if not isinstance(genes, list): - genes = [genes] - pathways = get_shared_pathways_for_genes(genes=genes, client=client) - return jsonify([p.to_json() for p in pathways]) - - -@app.route("/get_genes_for_pathway", methods=["POST"]) -def genes_for_pathway(): - """Get genes for a pathway.""" - pathway = _post_request_preproc("pathway") - genes = get_genes_for_pathway(pathway=pathway, client=client) - return jsonify([g.to_json() for g in genes]) - - -@app.route("/is_gene_in_pathway", methods=["POST"]) -def gene_in_pathway(): - """Check if a gene is in a pathway.""" - gene, pathway = _post_request_preproc("gene", "pathway") - return jsonify( - { - "gene_in_pathway": is_gene_in_pathway( - gene=gene, pathway=pathway, client=client - ) - } - ) - - -@app.route("/get_side_effects_for_drug", methods=["POST"]) -def side_effects_for_drug(): - """Get side effects for a drug.""" - drug = _post_request_preproc("drug") - side_effects = get_side_effects_for_drug(drug=drug, client=client) - return jsonify([s.to_json() for s in side_effects]) - - -@app.route("/get_drugs_for_side_effect", methods=["POST"]) -def drugs_for_side_effect(): - """Get drugs for a side effect.""" - side_effect = _post_request_preproc("side_effect") - drugs = get_drugs_for_side_effect(side_effect=side_effect, client=client) - return jsonify([d.to_json() for d in drugs]) - - -@app.route("/is_side_effect_for_drug", methods=["POST"]) -def side_effect_for_drug(): - """Check if a side effect is for a drug.""" - drug, side_effect = _post_request_preproc("drug", "side_effect") - return jsonify( - { - "side_effect_for_drug": is_side_effect_for_drug( - drug=drug, side_effect=side_effect, client=client - ) - } - ) - - -@app.route("/get_ontology_child_terms", methods=["POST"]) -def ontology_child_terms(): - """Get child terms of a term.""" - term = _post_request_preproc("term") - child_terms = get_ontology_child_terms(term=term, client=client) - return jsonify([t.to_json() for t in child_terms]) - - -@app.route("/get_ontology_parent_terms", methods=["POST"]) -def ontology_parent_terms(): - """Get parent terms of a term.""" - term = _post_request_preproc("term") - parent_terms = get_ontology_parent_terms(term=term, client=client) - return jsonify([t.to_json() for t in parent_terms]) - - -@app.route("/isa_or_partof", methods=["POST"]) -def isa_or_partof(): - """Check if a term is a part of another term.""" - term, parent_term = _post_request_preproc("term", "parent_term") - return jsonify( - { - "isa_or_partof": isa_or_partof( - term=term, parent_term=parent_term, client=client - ) - } - ) - - -@app.route("/get_pmids_for_mesh", methods=["POST"]) -def pmids_for_mesh(): - """Get pmids for a mesh term.""" - mesh_term = _post_request_preproc("mesh_term") - pmids = get_pmids_for_mesh(mesh_term=mesh_term, client=client) - return jsonify([p.to_json() for p in pmids]) - - -@app.route("/get_mesh_ids_for_pmid", methods=["POST"]) -def mesh_ids_for_pmid(): - """Get mesh ids for a pmid.""" - pmid_term = _post_request_preproc("pmid_term") - mesh_ids = get_mesh_ids_for_pmid(pmid_term=pmid_term, client=client) - return jsonify([m.to_json() for m in mesh_ids]) - - -@app.route("/get_evidences_for_mesh", methods=["POST"]) -def evidences_for_mesh(): - """Get evidences for a mesh term.""" - mesh_term = _post_request_preproc("mesh_term") - evidence_dict = get_evidences_for_mesh(mesh_term=mesh_term, client=client) - return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) - - -@app.route("/get_evidences_for_stmt_hash", methods=["POST"]) -def evidences_for_stmt_hash(): - """Get evidences for a statement hash.""" - stmt_hash = _post_request_preproc("stmt_hash") - evidences = get_evidences_for_stmt_hash(stmt_hash=stmt_hash, client=client) - return jsonify([e.to_json() for e in evidences]) - - -@app.route("/get_evidences_for_stmt_hashes", methods=["POST"]) -def evidences_for_stmt_hashes(): - """Get evidences for a list of statement hashes.""" - stmt_hashes = _post_request_preproc("stmt_hashes") - evidence_dict = get_evidences_for_stmt_hashes( - stmt_hashes=stmt_hashes, client=client - ) - return jsonify({h: [e.to_json() for e in el] for h, el in evidence_dict.items()}) - - -@app.route("/get_stmts_for_pmid", methods=["POST"]) -def stmts_for_pmid(): - """Get statements for a pmid.""" - pmid_term = _post_request_preproc("pmid_term") - stmts = get_stmts_for_pmid(pmid_term=pmid_term, client=client) - return jsonify([s.to_json() for s in stmts]) - - -@app.route("/get_stmts_for_mesh", methods=["POST"]) -def stmts_for_mesh(): - """Get statements for a mesh term.""" - mesh_term = _post_request_preproc("mesh_term") - stmts = get_stmts_for_mesh(mesh_term=mesh_term, client=client) - return jsonify([s.to_json() for s in stmts]) - - -@app.route("/get_stmts_for_stmt_hashes", methods=["POST"]) -def stmts_for_stmt_hashes(): - """Get statements for a list of statement hashes.""" - stmt_hashes = _post_request_preproc("stmt_hashes") - stmts = get_stmts_for_stmt_hashes(stmt_hashes=stmt_hashes, client=client) - return jsonify([s.to_json() for s in stmts]) - - -@app.route("/is_gene_mutated", methods=["POST"]) -def gene_mutated(): - """Check if a gene is mutated in a cell line""" - gene, cell_line = _post_request_preproc("gene", "cell_line") - return jsonify( - { - "is_gene_mutated": is_gene_mutated( - gene=gene, cell_line=cell_line, client=client - ) - } - ) - - -@app.route("/get_drugs_for_target", methods=["POST"]) -def drugs_for_target(): - """Get drugs for a target.""" - target = _post_request_preproc("target") - drugs = get_drugs_for_target(target=target, client=client) - return jsonify([d.to_json() for d in drugs]) - - -@app.route("/get_targets_for_drug", methods=["POST"]) -def targets_for_drug(): - """Get targets for a drug.""" - drug = _post_request_preproc("drug") - targets = get_targets_for_drug(drug=drug, client=client) - return jsonify([t.to_json() for t in targets]) - - -@app.route("/is_drug_target", methods=["POST"]) -def drug_target(): - """Check if the drug targets the given protein.""" - drug, target = _post_request_preproc("drug", "target") - return jsonify( - {"is_drug_target": is_drug_target(drug=drug, target=target, client=client)} - ) + """Get a query.""" + json_dict = request.json + result = func_mapping[self.func_name](**json_dict, client=client) + return jsonify(result) + + post.__doc__ = "This is the full __doc__" cli = make_web_command(app=app) From 7988d5373ddfc72d02a7ecc664c9f6a4f540e944 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 14:15:26 -0500 Subject: [PATCH 15/41] Add function to get docstring --- .../apps/query_web_app/__init__.py | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index b2287336b..59aacfc99 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -32,20 +32,47 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: parsed_doc = parse(fun.__doc__) + sig = signature(fun) - long = parsed_doc.short_description + "\n\n" + full_docstr = """{title} - if parsed_doc.long_description: - long += parsed_doc.long_description + "\n\n" +Parameters +---------- +{params} - long += "Parameters\n----------\n" +Returns +------- +{return_str} +""" + # Get title + short = parsed_doc.short_description + + param_templ = "{name} : {typing}\n {description}" + + ret_templ = "name : {typing}\n {description}" + + # Get the parameters + param_list = [] for param in parsed_doc.params: - long += param.arg_name + ": " + param.description + "\n" - long += "\n" - long += "Returns\n-------\n" - long += parsed_doc.returns.description + if param.arg_name == "client": + continue + + str_type = str(sig.parameters[param.arg_name].annotation).replace("typing.", "") + param_list.append( + param_templ.format( + name=param.arg_name, typing=str_type, description=param.description + ) + ) + params = "\n".join(param_list) + + return_str = ret_templ.format( + typing=str(sig.return_annotation).replace("typing.", ""), + description=parsed_doc.returns.description, + ) - return parsed_doc.short_description, parsed_doc.long_description + return short, full_docstr.format( + title=short, params=params, return_str=return_str, + ) func_mapping = {fname: getattr(queries, fname) for fname in queries.__all__} From 1e207e27b1109649e0af0bbd7dadaa8e7a632d60 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 14:19:47 -0500 Subject: [PATCH 16/41] Actaully apply docstrings to endpoint, formatting --- src/indra_cogex/apps/query_web_app/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 59aacfc99..86a9b359b 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -71,7 +71,9 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: ) return short, full_docstr.format( - title=short, params=params, return_str=return_str, + title=short, + params=params, + return_str=return_str, ) @@ -89,23 +91,23 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: if client_param is None: continue - short_doc, doc = get_docstring(func) + short_doc, fixed_doc = get_docstring(func) param_names = list(func_sig.parameters.keys()) param_names.remove("client") - name_title = func_name.replace("_", " ").title() + model_name = f"{func_name} model" # Create query model, separate between one and two parameter expectations if len(func_sig.parameters) == 2: # Get the parameters name for the other parameter that is not 'client' query_model = api.model( - f"{func_name}_endpoint", {param_names[0]: fields.List(fields.String)} + model_name, {param_names[0]: fields.List(fields.String)} ) elif len(func_sig.parameters) == 3: query_model = api.model( - f"{func_name}_endpoint", + model_name, { param_names[0]: fields.List(fields.String), param_names[1]: fields.List(fields.String), @@ -118,7 +120,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: ) @query_ns.expect(query_model) - @query_ns.route(f"/{func_name}", doc={"summary": "This is the summary"}) + @query_ns.route(f"/{func_name}", doc={"summary": short_doc}) class QueryResource(Resource): """A resource for a query.""" @@ -130,7 +132,7 @@ def post(self): result = func_mapping[self.func_name](**json_dict, client=client) return jsonify(result) - post.__doc__ = "This is the full __doc__" + post.__doc__ = fixed_doc cli = make_web_command(app=app) From facabb60f7d73f214dc504eb94daa2e262eadd40 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 14:48:07 -0500 Subject: [PATCH 17/41] Add return type mapper --- .../apps/query_web_app/__init__.py | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 86a9b359b..5eeea3cdf 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -2,18 +2,19 @@ """An app wrapping the query module of indra_cogex.""" import logging -from inspect import isfunction, signature -from typing import Callable, Tuple, Union +from inspect import isfunction, signature, Signature +from typing import Callable, Tuple, Type, Iterable, Any, Dict, List, Counter import flask from docstring_parser import parse -from flask import request, Response, jsonify -from flask_restx import Api, Resource, fields, abort +from flask import request, jsonify, abort, Response +from flask_restx import Api, Resource, fields from more_click import make_web_command +from indra.statements import Evidence, Statement, Agent from indra_cogex.client.neo4j_client import Neo4jClient from indra_cogex.client import queries - +from indra_cogex.representation import Node app = flask.Flask(__name__) api = Api( @@ -26,8 +27,39 @@ logger = logging.getLogger(__name__) -Tup = Tuple[str, str] -TupOfTups = Tuple[Tup, ...] + +def get_web_return_annotation(sig: Signature) -> Type: + """Get and translate the return annotation of a function.""" + # Get the return annotation + return_annotation = sig.return_annotation + if return_annotation is sig.empty: + raise ValueError("Forgot to type annotate function") + + # Translate the return annotation: + # Iterable[Node] -> List[Dict[str, Any]] + # bool -> Dict[str: bool] + # Dict[str, List[Evidence]] -> Dict[int, List[Dict[str, Any]]] + # Iterable[Evidence] -> List[Dict[str, Any]] + # Iterable[Statement] -> List[Dict[int, Any]] + # Counter -> Dict[str, int] + # Iterable[Agent] -> List[Dict[str, Any]] + + if return_annotation is Iterable[Node]: + return List[Dict[str, Any]] + elif return_annotation is bool: + return Dict[str, bool] + elif return_annotation is Dict[str, List[Evidence]]: + return Dict[str, List[Dict[str, Any]]] + elif return_annotation is Iterable[Evidence]: + return List[Dict[str, Any]] + elif return_annotation is Iterable[Statement]: + return List[Dict[str, Any]] + elif return_annotation is Counter: + return Dict[str, int] + elif return_annotation is Iterable[Agent]: + return List[Dict[str, Any]] + else: + return return_annotation def get_docstring(fun: Callable) -> Tuple[str, str]: @@ -66,7 +98,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: params = "\n".join(param_list) return_str = ret_templ.format( - typing=str(sig.return_annotation).replace("typing.", ""), + typing=str(get_web_return_annotation(sig)).replace("typing.", ""), description=parsed_doc.returns.description, ) From 32eb1f7e61a9359c76e5436c1af00d25b50be35e Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 14:49:45 -0500 Subject: [PATCH 18/41] Handle TypeError --- src/indra_cogex/apps/query_web_app/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 5eeea3cdf..bada77f73 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -161,8 +161,16 @@ class QueryResource(Resource): def post(self): """Get a query.""" json_dict = request.json - result = func_mapping[self.func_name](**json_dict, client=client) - return jsonify(result) + if json_dict is None: + abort(Response("Missing application/json header.", 415)) + try: + result = func_mapping[self.func_name](**json_dict, client=client) + + return jsonify(result) + + except TypeError as err: + logger.error(err) + abort(Response(str(err), 415)) post.__doc__ = fixed_doc From 933bd9dad4aa2864a93f0474b79793ed842a70f3 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 15:21:04 -0500 Subject: [PATCH 19/41] Add helper to process results. Use it. --- .../apps/query_web_app/__init__.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index bada77f73..15773af79 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -3,7 +3,8 @@ """An app wrapping the query module of indra_cogex.""" import logging from inspect import isfunction, signature, Signature -from typing import Callable, Tuple, Type, Iterable, Any, Dict, List, Counter +from typing import Callable, Tuple, Type, Iterable, Any, Dict, List, Counter, \ + Mapping import flask from docstring_parser import parse @@ -28,6 +29,24 @@ logger = logging.getLogger(__name__) +def process_result(result) -> Any: + # Any fundamental type + if isinstance(result, (int, str, bool)): + return result + # Any iterable query + elif isinstance(result, (Iterable, list, set)): + list_res = list(result) + if hasattr(list_res[0], "to_json"): + list_res = [res.to_json() for res in list_res] + return list_res + # Any dict query + elif isinstance(result, (dict, Mapping, Counter)): + res_dict = dict(result) + return {k: process_result(v) for k, v in res_dict.items()} + else: + raise TypeError(f"Don't know how to process result of type {type(result)}") + + def get_web_return_annotation(sig: Signature) -> Type: """Get and translate the return annotation of a function.""" # Get the return annotation @@ -165,9 +184,11 @@ def post(self): abort(Response("Missing application/json header.", 415)) try: result = func_mapping[self.func_name](**json_dict, client=client) - - return jsonify(result) - + # Any 'is' type query + if isinstance(result, bool): + return jsonify({func_name: result}) + else: + return jsonify(process_result(result)) except TypeError as err: logger.error(err) abort(Response(str(err), 415)) From f27b5422722621b70809b2b5fea4d34c7ca180d6 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 15:21:27 -0500 Subject: [PATCH 20/41] Add another newline between parameters to docstring --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 15773af79..dcc60e115 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -114,7 +114,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: name=param.arg_name, typing=str_type, description=param.description ) ) - params = "\n".join(param_list) + params = "\n\n".join(param_list) return_str = ret_templ.format( typing=str(get_web_return_annotation(sig)).replace("typing.", ""), From d9a41a30967744888320d104d91f11ad93ca2898 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 15:22:48 -0500 Subject: [PATCH 21/41] Fix bug --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index dcc60e115..037996e6f 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -186,7 +186,7 @@ def post(self): result = func_mapping[self.func_name](**json_dict, client=client) # Any 'is' type query if isinstance(result, bool): - return jsonify({func_name: result}) + return jsonify({self.func_name: result}) else: return jsonify(process_result(result)) except TypeError as err: From eb90d03da9cc75d25515e70e66948af6a29d4111 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 15:37:37 -0500 Subject: [PATCH 22/41] Skip optional param evidence_map --- src/indra_cogex/apps/query_web_app/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 037996e6f..0d40908f1 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -105,7 +105,8 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: # Get the parameters param_list = [] for param in parsed_doc.params: - if param.arg_name == "client": + # Skip client, evidence_map, + if param.arg_name in ("client", 'evidence_map'): continue str_type = str(sig.parameters[param.arg_name].annotation).replace("typing.", "") From 363767e7df1aac091fca64e95c0737a437f21f1d Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 16:23:39 -0500 Subject: [PATCH 23/41] Fix text --- tests/test_queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_queries.py b/tests/test_queries.py index ffcafaf66..1205420a7 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -315,7 +315,7 @@ def test_get_stmts_for_mesh_id_wo_children(): def test_get_stmts_by_hashes(): # Note: This statement has a ~500 of evidences # Two queries: first statements, then all the evidence for the statements - stmt_hashes = ["35279776755000170"] + stmt_hashes = [35279776755000170] client = _get_client() stmts = get_stmts_for_stmt_hashes(stmt_hashes, client=client) assert stmts From 6a305a07819e064281d6f49467f8a02dfafc63b3 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 16:29:49 -0500 Subject: [PATCH 24/41] Add examples. Run black. --- .../apps/query_web_app/__init__.py | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 0d40908f1..df9120e9f 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -3,8 +3,7 @@ """An app wrapping the query module of indra_cogex.""" import logging from inspect import isfunction, signature, Signature -from typing import Callable, Tuple, Type, Iterable, Any, Dict, List, Counter, \ - Mapping +from typing import Callable, Tuple, Type, Iterable, Any, Dict, List, Counter, Mapping import flask from docstring_parser import parse @@ -29,6 +28,30 @@ logger = logging.getLogger(__name__) +examples_dict = { + "tissue": ["UBERON", "UBERON:0002349"], + "gene": ["HGNC", "9896"], + "go_term": ["GO", "GO:0000978"], + "drug": ["CHEBI", "CHEBI:27690"], + "disease": ["MESH", "D007855"], + "trial": ["CLINICALTRIALS", "NCT00000114"], + "genes": [["HGNC", "1097"], ["HGNC", "6407"]], + "pathway": ["WIKIPATHWAYS", "WP5037"], + "side_effect": ["UMLS", "C3267206"], + "term": ["MESH", "D007855"], + "parent": ["MESH", "D007855"], + "mesh_term": ["MESH", "D015002"], + "pmid_term": ["PUBMED", "27890007"], + "include_child_terms": True, + "stmt_hash": 12198579805553967, + "stmt_hashes": [12198579805553967, 30651649296901235], + "cell_line": ["CCLE", "BT20_BREAST"], + "target": ["HGNC", "6840"], + "include_indirect": True, + "evidence_map": {} +} + + def process_result(result) -> Any: # Any fundamental type if isinstance(result, (int, str, bool)): @@ -106,7 +129,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: param_list = [] for param in parsed_doc.params: # Skip client, evidence_map, - if param.arg_name in ("client", 'evidence_map'): + if param.arg_name in ("client", "evidence_map"): continue str_type = str(sig.parameters[param.arg_name].annotation).replace("typing.", "") @@ -154,15 +177,24 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: if len(func_sig.parameters) == 2: # Get the parameters name for the other parameter that is not 'client' query_model = api.model( - model_name, {param_names[0]: fields.List(fields.String)} + model_name, + { + param_names[0]: fields.List( + fields.String, example=examples_dict[param_names[0]] + ) + }, ) elif len(func_sig.parameters) == 3: query_model = api.model( model_name, { - param_names[0]: fields.List(fields.String), - param_names[1]: fields.List(fields.String), + param_names[0]: fields.List( + fields.String, example=examples_dict[param_names[0]] + ), + param_names[1]: fields.List( + fields.String, example=examples_dict[param_names[1]] + ), }, ) else: From c262e51ffae8ad4d97e8c6e024bea067dc6340b2 Mon Sep 17 00:00:00 2001 From: kkaris Date: Mon, 28 Feb 2022 16:41:57 -0500 Subject: [PATCH 25/41] Fix return type --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index df9120e9f..f65bf71c7 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -90,7 +90,7 @@ def get_web_return_annotation(sig: Signature) -> Type: return List[Dict[str, Any]] elif return_annotation is bool: return Dict[str, bool] - elif return_annotation is Dict[str, List[Evidence]]: + elif return_annotation is Dict[int, List[Evidence]]: return Dict[str, List[Dict[str, Any]]] elif return_annotation is Iterable[Evidence]: return List[Dict[str, Any]] From 708b3163a5f872ff727e953cfdc297e63aec24a6 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 10:16:38 -0500 Subject: [PATCH 26/41] Specify return type without a variable name --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index f65bf71c7..ae4304471 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -123,7 +123,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: param_templ = "{name} : {typing}\n {description}" - ret_templ = "name : {typing}\n {description}" + ret_templ = "{typing}\n {description}" # Get the parameters param_list = [] From 452ef5b0a48b179a32176f3da7f525b911e6cdbc Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 10:21:46 -0500 Subject: [PATCH 27/41] Set path for swagger docs to /docs. Format code. --- src/indra_cogex/apps/query_web_app/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index ae4304471..c1f3bdcb4 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -18,7 +18,10 @@ app = flask.Flask(__name__) api = Api( - app, title="INDRA CoGEx Query API", description="REST API for INDRA CoGEx queries" + app, + title="INDRA CoGEx Query API", + description="REST API for INDRA CoGEx queries", + doc="/docs", ) query_ns = api.namespace("CoGEx Queries", "Queries for INDRA CoGEx", path="/api/") @@ -48,7 +51,7 @@ "cell_line": ["CCLE", "BT20_BREAST"], "target": ["HGNC", "6840"], "include_indirect": True, - "evidence_map": {} + "evidence_map": {}, } From 3eccf931b315e0fe38650fe86dd8c2c4b54980a4 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 11:28:50 -0500 Subject: [PATCH 28/41] Revert to default api docs path --- src/indra_cogex/apps/query_web_app/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index c1f3bdcb4..0a18b1073 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -21,7 +21,6 @@ app, title="INDRA CoGEx Query API", description="REST API for INDRA CoGEx queries", - doc="/docs", ) query_ns = api.namespace("CoGEx Queries", "Queries for INDRA CoGEx", path="/api/") From de23b3f325056d636bd794b38adc587f62c9886c Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 12:28:03 -0500 Subject: [PATCH 29/41] Fix model name --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 0a18b1073..5273d49de 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -173,7 +173,7 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: param_names = list(func_sig.parameters.keys()) param_names.remove("client") - model_name = f"{func_name} model" + model_name = f"{func_name}_model" # Create query model, separate between one and two parameter expectations if len(func_sig.parameters) == 2: From f204e339706bd0d8b238b328d80b79463c98f335 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 12:50:06 -0500 Subject: [PATCH 30/41] Fix JS int problem by stringifying --- .../apps/query_web_app/__init__.py | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 5273d49de..363d65c7c 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -45,8 +45,9 @@ "mesh_term": ["MESH", "D015002"], "pmid_term": ["PUBMED", "27890007"], "include_child_terms": True, - "stmt_hash": 12198579805553967, - "stmt_hashes": [12198579805553967, 30651649296901235], + # NOTE: statement hashes are too large to be int for JavaScript + "stmt_hash": "12198579805553967", + "stmt_hashes": ["12198579805553967", "30651649296901235"], "cell_line": ["CCLE", "BT20_BREAST"], "target": ["HGNC", "6840"], "include_indirect": True, @@ -54,6 +55,34 @@ } +def parse_json(query_json: Dict[str, Any]) -> Dict[str, Any]: + """Parse the incoming query + + Parameters + ---------- + query_json : + The incoming query as a dictionary + + Returns + ------- + : + The parsed query + """ + parsed_query = {} + for key, value in query_json.items(): + if key in ('stmt_hashes', 'stmt_hash'): + if isinstance(value, str): + parsed_query[key] = int(value) + elif isinstance(value, list): + parsed_query[key] = [int(v) for v in value] + else: + raise ValueError(f"{key} must be a string or list of strings") + else: + parsed_query[key] = value + + return parsed_query + + def process_result(result) -> Any: # Any fundamental type if isinstance(result, (int, str, bool)): @@ -134,7 +163,14 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: if param.arg_name in ("client", "evidence_map"): continue - str_type = str(sig.parameters[param.arg_name].annotation).replace("typing.", "") + if param.arg_name == 'stmt_hash': + annot = str + elif param.arg_name == 'stmt_hashes': + annot = List[str] + else: + annot = sig.parameters[param.arg_name].annotation + str_type = str(annot).replace("typing.", "") + param_list.append( param_templ.format( name=param.arg_name, typing=str_type, description=param.description @@ -218,7 +254,8 @@ def post(self): if json_dict is None: abort(Response("Missing application/json header.", 415)) try: - result = func_mapping[self.func_name](**json_dict, client=client) + parsed_query = parse_json(json_dict) + result = func_mapping[self.func_name](**parsed_query, client=client) # Any 'is' type query if isinstance(result, bool): return jsonify({self.func_name: result}) From ec3cf46db3af63d7648d715bf234e1b6af407afd Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 13:08:13 -0500 Subject: [PATCH 31/41] Check for empty list before checking to_json --- src/indra_cogex/apps/query_web_app/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 363d65c7c..21e188de7 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -90,7 +90,8 @@ def process_result(result) -> Any: # Any iterable query elif isinstance(result, (Iterable, list, set)): list_res = list(result) - if hasattr(list_res[0], "to_json"): + # Check for empty list + if list_res and hasattr(list_res[0], "to_json"): list_res = [res.to_json() for res in list_res] return list_res # Any dict query From 03ed3fcd23f7039c5fe4d3e0e12b30b9218bdf49 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 13:11:42 -0500 Subject: [PATCH 32/41] Catch other errors than TypeError --- src/indra_cogex/apps/query_web_app/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 21e188de7..79b83a715 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -266,6 +266,10 @@ def post(self): logger.error(err) abort(Response(str(err), 415)) + except Exception as err: + logger.error(err) + abort(Response(str(err), 500)) + post.__doc__ = fixed_doc From 996b4f3e3e888917edbb0f95cd31343c4683112d Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 13:50:45 -0500 Subject: [PATCH 33/41] Catch missing example --- .../apps/query_web_app/__init__.py | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 79b83a715..85470e082 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -52,6 +52,7 @@ "target": ["HGNC", "6840"], "include_indirect": True, "evidence_map": {}, + "filter_medscan": True, } @@ -213,34 +214,37 @@ def get_docstring(fun: Callable) -> Tuple[str, str]: model_name = f"{func_name}_model" # Create query model, separate between one and two parameter expectations - if len(func_sig.parameters) == 2: - # Get the parameters name for the other parameter that is not 'client' - query_model = api.model( - model_name, - { - param_names[0]: fields.List( - fields.String, example=examples_dict[param_names[0]] - ) - }, - ) - elif len(func_sig.parameters) == 3: - - query_model = api.model( - model_name, - { - param_names[0]: fields.List( - fields.String, example=examples_dict[param_names[0]] - ), - param_names[1]: fields.List( - fields.String, example=examples_dict[param_names[1]] - ), - }, - ) - else: - raise ValueError( - f"Query function {func_name} has an unexpected number of " - f"parameters ({len(func_sig.parameters)})" - ) + try: + if len(func_sig.parameters) == 2: + # Get the parameters name for the other parameter that is not 'client' + query_model = api.model( + model_name, + { + param_names[0]: fields.List( + fields.String, example=examples_dict[param_names[0]] + ) + }, + ) + elif len(func_sig.parameters) == 3: + + query_model = api.model( + model_name, + { + param_names[0]: fields.List( + fields.String, example=examples_dict[param_names[0]] + ), + param_names[1]: fields.List( + fields.String, example=examples_dict[param_names[1]] + ), + }, + ) + else: + raise ValueError( + f"Query function {func_name} has an unexpected number of " + f"parameters ({len(func_sig.parameters)})" + ) + except KeyError as err: + raise KeyError(f"No examples for {func_name}, please add one") from err @query_ns.expect(query_model) @query_ns.route(f"/{func_name}", doc={"summary": short_doc}) From 8ba31179549ee57aab3a5f786b50796940677a7c Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:20:57 -0500 Subject: [PATCH 34/41] Catch missing example --- src/indra_cogex/client/queries.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index b4ad840e1..84ab162d9 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -774,7 +774,7 @@ def get_evidences_for_stmt_hash( % stmt_hash ) ev_jsons = [json.loads(r[0]) for r in client.query_tx(query)] - return [Evidence._from_json(ev_json) for ev_json in ev_jsons] + return _filter_out_medscan_evidence(ev_list=ev_jsons) @autoclient() @@ -973,6 +973,7 @@ def _get_mesh_child_terms( def _get_ev_dict_from_hash_ev_query( result: Optional[Iterable[List[Union[int, str]]]] = None, + remove_medscan: bool = True, ) -> Dict[int, List[Evidence]]: """Assumes `result` is an Iterable of pairs of [hash, evidence_json]""" if result is None: @@ -982,6 +983,8 @@ def _get_ev_dict_from_hash_ev_query( ev_dict = defaultdict(list) for stmt_hash, ev_json_str in result: ev_json = json.loads(ev_json_str) + if remove_medscan and ev_json["source_api"] == "medscan": + continue ev_dict[stmt_hash].append(Evidence._from_json(ev_json)) return dict(ev_dict) @@ -1221,3 +1224,14 @@ def _get_node_from_stmt_relation( stmt_json = json.loads(rel.data["stmt_json"]) name = stmt_json[agent_role]["name"] return Node(node_ns, node_id, ["BioEntity"], dict(name=name)) + + +def _filter_out_medscan_evidence( + ev_list: Iterable[Dict[str, Dict]], remove_medscan: bool = True +) -> Iterable[Evidence]: + """Filter out Evidence JSONs containing evidence from MedScan.""" + return [ + Evidence._from_json(ev) + for ev in ev_list + if not (remove_medscan and ev["source_api"] == "medscan") + ] From 2e4123e3838b53905e4d64f0441ec821b8bed7d0 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:22:12 -0500 Subject: [PATCH 35/41] Move helper to where other helpers are --- src/indra_cogex/client/queries.py | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 84ab162d9..152f0a253 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -971,24 +971,6 @@ def _get_mesh_child_terms( return {c[0] for c in client.query_tx(query)} -def _get_ev_dict_from_hash_ev_query( - result: Optional[Iterable[List[Union[int, str]]]] = None, - remove_medscan: bool = True, -) -> Dict[int, List[Evidence]]: - """Assumes `result` is an Iterable of pairs of [hash, evidence_json]""" - if result is None: - logger.warning("No result for hash, Evidence query, returning empty dict") - return {} - - ev_dict = defaultdict(list) - for stmt_hash, ev_json_str in result: - ev_json = json.loads(ev_json_str) - if remove_medscan and ev_json["source_api"] == "medscan": - continue - ev_dict[stmt_hash].append(Evidence._from_json(ev_json)) - return dict(ev_dict) - - @autoclient(cache=True) def get_node_counter(*, client: Neo4jClient) -> Counter: """Get a count of each entity type. @@ -1210,6 +1192,24 @@ def is_drug_target( return any(_is_drug_relation(rel) for rel in rels) +def _get_ev_dict_from_hash_ev_query( + result: Optional[Iterable[List[Union[int, str]]]] = None, + remove_medscan: bool = True, +) -> Dict[int, List[Evidence]]: + """Assumes `result` is an Iterable of pairs of [hash, evidence_json]""" + if result is None: + logger.warning("No result for hash, Evidence query, returning empty dict") + return {} + + ev_dict = defaultdict(list) + for stmt_hash, ev_json_str in result: + ev_json = json.loads(ev_json_str) + if remove_medscan and ev_json["source_api"] == "medscan": + continue + ev_dict[stmt_hash].append(Evidence._from_json(ev_json)) + return dict(ev_dict) + + def _is_drug_relation(rel: Relation) -> bool: """Return True if the relation is a drug-target relation.""" return rel.data["stmt_type"] == "Inhibition" and "tas" in rel.data["source_counts"] From 3c571f7a3fb5feb18b23b1fab82c20300be586be Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:27:47 -0500 Subject: [PATCH 36/41] Remove unused import --- src/indra_cogex/client/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 152f0a253..4d3cd5369 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -4,7 +4,7 @@ from typing import Dict, Iterable, List, Mapping, Optional, Set, Tuple, Union import networkx as nx -from indra.statements import Agent, Evidence, Statement, stmts_from_json +from indra.statements import Agent, Evidence, Statement from .neo4j_client import Neo4jClient, autoclient from ..representation import Node, Relation, indra_stmts_from_relations, norm_id From 747d3593cff06e4628d4b0ba5b875366af951b80 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:51:04 -0500 Subject: [PATCH 37/41] Fix typing --- src/indra_cogex/client/queries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 4d3cd5369..2dcbec197 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -1228,8 +1228,8 @@ def _get_node_from_stmt_relation( def _filter_out_medscan_evidence( ev_list: Iterable[Dict[str, Dict]], remove_medscan: bool = True -) -> Iterable[Evidence]: - """Filter out Evidence JSONs containing evidence from MedScan.""" +) -> List[Evidence]: + """Filter out Evidence JSONs containing evidence from medscan.""" return [ Evidence._from_json(ev) for ev in ev_list From 64b5ea9b053a67c681edce6e528dbfdf7c89cd55 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:57:24 -0500 Subject: [PATCH 38/41] Explicitly set revmove_medscan=True for now --- src/indra_cogex/client/queries.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/indra_cogex/client/queries.py b/src/indra_cogex/client/queries.py index 2dcbec197..3d185fab4 100644 --- a/src/indra_cogex/client/queries.py +++ b/src/indra_cogex/client/queries.py @@ -747,7 +747,7 @@ def get_evidences_for_mesh( single_mesh_match, where_clause, ) - return _get_ev_dict_from_hash_ev_query(client.query_tx(query)) + return _get_ev_dict_from_hash_ev_query(client.query_tx(query), remove_medscan=True) @autoclient() @@ -774,7 +774,7 @@ def get_evidences_for_stmt_hash( % stmt_hash ) ev_jsons = [json.loads(r[0]) for r in client.query_tx(query)] - return _filter_out_medscan_evidence(ev_list=ev_jsons) + return _filter_out_medscan_evidence(ev_list=ev_jsons, remove_medscan=True) @autoclient() @@ -806,7 +806,7 @@ def get_evidences_for_stmt_hashes( % stmt_hashes_str ) - return _get_ev_dict_from_hash_ev_query(client.query_tx(query)) + return _get_ev_dict_from_hash_ev_query(client.query_tx(query), remove_medscan=True) @autoclient() @@ -844,7 +844,7 @@ def get_stmts_for_pmid( % pmid_norm ) result = client.query_tx(hash_query) - ev_dict = _get_ev_dict_from_hash_ev_query(result) + ev_dict = _get_ev_dict_from_hash_ev_query(result, remove_medscan=True) stmt_hashes = set(ev_dict.keys()) return get_stmts_for_stmt_hashes(stmt_hashes, ev_dict, client=client) From 22719a65f7a165b1ab57ac86379e07684c620273 Mon Sep 17 00:00:00 2001 From: kkaris Date: Tue, 1 Mar 2022 14:57:43 -0500 Subject: [PATCH 39/41] Add tests for removing medscan --- tests/test_queries.py | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/test_queries.py b/tests/test_queries.py index 1205420a7..e36093e69 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -1,7 +1,13 @@ +import json + import pytest from indra.statements import Statement, Evidence from indra_cogex.client.queries import * +from indra_cogex.client.queries import ( + _filter_out_medscan_evidence, + _get_ev_dict_from_hash_ev_query, +) from indra_cogex.representation import Node from .test_neo4j_client import _get_client @@ -360,3 +366,49 @@ def test_is_drug_target(): assert is_drug_target(drug, target, client=client) wrong_target = ("HGNC", "6407") assert not is_drug_target(drug, wrong_target, client=client) + + +def test_filter_out_medscan_evidence(): + ev = Evidence(source_api="reach").to_json() + medscan_ev = Evidence(source_api="medscan").to_json() + + ev_list = _filter_out_medscan_evidence([ev, medscan_ev], remove_medscan=True) + assert len(ev_list) == 1 + assert ev_list[0].equals(Evidence._from_json(ev)) + + ev_list = _filter_out_medscan_evidence([ev, medscan_ev], remove_medscan=False) + assert len(ev_list) == 2 + assert ev_list[0].equals(Evidence._from_json(ev)) + assert ev_list[1].equals(Evidence._from_json(medscan_ev)) + + ev_list = _filter_out_medscan_evidence([medscan_ev], remove_medscan=True) + assert len(ev_list) == 0 + + +def test_get_ev_dict_from_hash_ev_query(): + ev = Evidence(source_api="reach").to_json() + medscan_ev = Evidence(source_api="medscan").to_json() + + ev_dict = _get_ev_dict_from_hash_ev_query( + [[123456, json.dumps(ev)], [654321, json.dumps(medscan_ev)]], + remove_medscan=True, + ) + assert ev_dict[123456] + assert ev_dict[123456][0].equals(Evidence._from_json(ev)) + assert 654321 not in ev_dict + + ev_dict = _get_ev_dict_from_hash_ev_query( + [[123456, json.dumps(ev)], [654321, json.dumps(medscan_ev)]], + remove_medscan=False, + ) + assert ev_dict[123456] + assert ev_dict[123456][0].equals(Evidence._from_json(ev)) + assert ev_dict[654321] + assert ev_dict[654321][0].equals(Evidence._from_json(medscan_ev)) + + ev_dict = _get_ev_dict_from_hash_ev_query( + [[654321, json.dumps(medscan_ev)]], + remove_medscan=True, + ) + assert 654321 not in ev_dict + assert not ev_dict From bfad9aba3bc0cfb785c874e02753d4955460df61 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 4 Mar 2022 11:45:27 -0500 Subject: [PATCH 40/41] Add float to fundamental type check --- src/indra_cogex/apps/query_web_app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index 85470e082..a3d564b82 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -86,7 +86,7 @@ def parse_json(query_json: Dict[str, Any]) -> Dict[str, Any]: def process_result(result) -> Any: # Any fundamental type - if isinstance(result, (int, str, bool)): + if isinstance(result, (int, str, bool, float)): return result # Any iterable query elif isinstance(result, (Iterable, list, set)): From ba71e2576a6766ac76d98a44ebacbe933a443ff7 Mon Sep 17 00:00:00 2001 From: kkaris Date: Fri, 4 Mar 2022 11:47:16 -0500 Subject: [PATCH 41/41] Safeguard by checking dict-like before iterables --- src/indra_cogex/apps/query_web_app/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/indra_cogex/apps/query_web_app/__init__.py b/src/indra_cogex/apps/query_web_app/__init__.py index a3d564b82..53f23180e 100644 --- a/src/indra_cogex/apps/query_web_app/__init__.py +++ b/src/indra_cogex/apps/query_web_app/__init__.py @@ -88,6 +88,10 @@ def process_result(result) -> Any: # Any fundamental type if isinstance(result, (int, str, bool, float)): return result + # Any dict query + elif isinstance(result, (dict, Mapping, Counter)): + res_dict = dict(result) + return {k: process_result(v) for k, v in res_dict.items()} # Any iterable query elif isinstance(result, (Iterable, list, set)): list_res = list(result) @@ -95,10 +99,6 @@ def process_result(result) -> Any: if list_res and hasattr(list_res[0], "to_json"): list_res = [res.to_json() for res in list_res] return list_res - # Any dict query - elif isinstance(result, (dict, Mapping, Counter)): - res_dict = dict(result) - return {k: process_result(v) for k, v in res_dict.items()} else: raise TypeError(f"Don't know how to process result of type {type(result)}")