tooluniverse 0.2.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +340 -4
- tooluniverse/admetai_tool.py +84 -0
- tooluniverse/agentic_tool.py +563 -0
- tooluniverse/alphafold_tool.py +96 -0
- tooluniverse/base_tool.py +129 -6
- tooluniverse/boltz_tool.py +207 -0
- tooluniverse/chem_tool.py +192 -0
- tooluniverse/compose_scripts/__init__.py +1 -0
- tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
- tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
- tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
- tooluniverse/compose_scripts/literature_tool.py +34 -0
- tooluniverse/compose_scripts/output_summarizer.py +279 -0
- tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
- tooluniverse/compose_scripts/tool_discover.py +705 -0
- tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
- tooluniverse/compose_tool.py +371 -0
- tooluniverse/ctg_tool.py +1002 -0
- tooluniverse/custom_tool.py +81 -0
- tooluniverse/dailymed_tool.py +108 -0
- tooluniverse/data/admetai_tools.json +155 -0
- tooluniverse/data/agentic_tools.json +1156 -0
- tooluniverse/data/alphafold_tools.json +87 -0
- tooluniverse/data/boltz_tools.json +9 -0
- tooluniverse/data/chembl_tools.json +16 -0
- tooluniverse/data/clait_tools.json +108 -0
- tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
- tooluniverse/data/compose_tools.json +202 -0
- tooluniverse/data/dailymed_tools.json +70 -0
- tooluniverse/data/dataset_tools.json +646 -0
- tooluniverse/data/disease_target_score_tools.json +712 -0
- tooluniverse/data/efo_tools.json +17 -0
- tooluniverse/data/embedding_tools.json +319 -0
- tooluniverse/data/enrichr_tools.json +31 -0
- tooluniverse/data/europe_pmc_tools.json +22 -0
- tooluniverse/data/expert_feedback_tools.json +10 -0
- tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
- tooluniverse/data/fda_drug_labeling_tools.json +1 -1
- tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
- tooluniverse/data/finder_tools.json +209 -0
- tooluniverse/data/gene_ontology_tools.json +113 -0
- tooluniverse/data/gwas_tools.json +1082 -0
- tooluniverse/data/hpa_tools.json +333 -0
- tooluniverse/data/humanbase_tools.json +47 -0
- tooluniverse/data/idmap_tools.json +74 -0
- tooluniverse/data/mcp_client_tools_example.json +113 -0
- tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
- tooluniverse/data/medlineplus_tools.json +141 -0
- tooluniverse/data/monarch_tools.json +1 -1
- tooluniverse/data/openalex_tools.json +36 -0
- tooluniverse/data/opentarget_tools.json +1 -1
- tooluniverse/data/output_summarization_tools.json +101 -0
- tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
- tooluniverse/data/packages/categorized_tools.txt +206 -0
- tooluniverse/data/packages/cheminformatics_tools.json +347 -0
- tooluniverse/data/packages/earth_sciences_tools.json +74 -0
- tooluniverse/data/packages/genomics_tools.json +776 -0
- tooluniverse/data/packages/image_processing_tools.json +38 -0
- tooluniverse/data/packages/machine_learning_tools.json +789 -0
- tooluniverse/data/packages/neuroscience_tools.json +62 -0
- tooluniverse/data/packages/original_tools.txt +0 -0
- tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
- tooluniverse/data/packages/scientific_computing_tools.json +560 -0
- tooluniverse/data/packages/single_cell_tools.json +453 -0
- tooluniverse/data/packages/software_tools.json +4954 -0
- tooluniverse/data/packages/structural_biology_tools.json +396 -0
- tooluniverse/data/packages/visualization_tools.json +399 -0
- tooluniverse/data/pubchem_tools.json +215 -0
- tooluniverse/data/pubtator_tools.json +68 -0
- tooluniverse/data/rcsb_pdb_tools.json +1332 -0
- tooluniverse/data/reactome_tools.json +19 -0
- tooluniverse/data/semantic_scholar_tools.json +26 -0
- tooluniverse/data/special_tools.json +2 -25
- tooluniverse/data/tool_composition_tools.json +88 -0
- tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
- tooluniverse/data/txagent_client_tools.json +9 -0
- tooluniverse/data/uniprot_tools.json +211 -0
- tooluniverse/data/url_fetch_tools.json +94 -0
- tooluniverse/data/uspto_downloader_tools.json +9 -0
- tooluniverse/data/uspto_tools.json +811 -0
- tooluniverse/data/xml_tools.json +3275 -0
- tooluniverse/dataset_tool.py +296 -0
- tooluniverse/default_config.py +165 -0
- tooluniverse/efo_tool.py +42 -0
- tooluniverse/embedding_database.py +630 -0
- tooluniverse/embedding_sync.py +396 -0
- tooluniverse/enrichr_tool.py +266 -0
- tooluniverse/europe_pmc_tool.py +52 -0
- tooluniverse/execute_function.py +1775 -95
- tooluniverse/extended_hooks.py +444 -0
- tooluniverse/gene_ontology_tool.py +194 -0
- tooluniverse/graphql_tool.py +158 -36
- tooluniverse/gwas_tool.py +358 -0
- tooluniverse/hpa_tool.py +1645 -0
- tooluniverse/humanbase_tool.py +389 -0
- tooluniverse/logging_config.py +254 -0
- tooluniverse/mcp_client_tool.py +764 -0
- tooluniverse/mcp_integration.py +413 -0
- tooluniverse/mcp_tool_registry.py +925 -0
- tooluniverse/medlineplus_tool.py +337 -0
- tooluniverse/openalex_tool.py +228 -0
- tooluniverse/openfda_adv_tool.py +283 -0
- tooluniverse/openfda_tool.py +393 -160
- tooluniverse/output_hook.py +1122 -0
- tooluniverse/package_tool.py +195 -0
- tooluniverse/pubchem_tool.py +158 -0
- tooluniverse/pubtator_tool.py +168 -0
- tooluniverse/rcsb_pdb_tool.py +38 -0
- tooluniverse/reactome_tool.py +108 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
- tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
- tooluniverse/remote/expert_feedback/simple_test.py +23 -0
- tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
- tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
- tooluniverse/remote/expert_feedback_mcp/human_expert_mcp_server.py +1611 -0
- tooluniverse/remote/expert_feedback_mcp/simple_test.py +34 -0
- tooluniverse/remote/expert_feedback_mcp/start_web_interface.py +91 -0
- tooluniverse/remote/immune_compass/compass_tool.py +327 -0
- tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
- tooluniverse/remote_tool.py +99 -0
- tooluniverse/restful_tool.py +53 -30
- tooluniverse/scripts/generate_tool_graph.py +408 -0
- tooluniverse/scripts/visualize_tool_graph.py +829 -0
- tooluniverse/semantic_scholar_tool.py +62 -0
- tooluniverse/smcp.py +2452 -0
- tooluniverse/smcp_server.py +975 -0
- tooluniverse/test/mcp_server_test.py +0 -0
- tooluniverse/test/test_admetai_tool.py +370 -0
- tooluniverse/test/test_agentic_tool.py +129 -0
- tooluniverse/test/test_alphafold_tool.py +71 -0
- tooluniverse/test/test_chem_tool.py +37 -0
- tooluniverse/test/test_compose_lieraturereview.py +63 -0
- tooluniverse/test/test_compose_tool.py +448 -0
- tooluniverse/test/test_dailymed.py +69 -0
- tooluniverse/test/test_dataset_tool.py +200 -0
- tooluniverse/test/test_disease_target_score.py +56 -0
- tooluniverse/test/test_drugbank_filter_examples.py +179 -0
- tooluniverse/test/test_efo.py +31 -0
- tooluniverse/test/test_enrichr_tool.py +21 -0
- tooluniverse/test/test_europe_pmc_tool.py +20 -0
- tooluniverse/test/test_fda_adv.py +95 -0
- tooluniverse/test/test_fda_drug_labeling.py +91 -0
- tooluniverse/test/test_gene_ontology_tools.py +66 -0
- tooluniverse/test/test_gwas_tool.py +139 -0
- tooluniverse/test/test_hpa.py +625 -0
- tooluniverse/test/test_humanbase_tool.py +20 -0
- tooluniverse/test/test_idmap_tools.py +61 -0
- tooluniverse/test/test_mcp_server.py +211 -0
- tooluniverse/test/test_mcp_tool.py +247 -0
- tooluniverse/test/test_medlineplus.py +220 -0
- tooluniverse/test/test_openalex_tool.py +32 -0
- tooluniverse/test/test_opentargets.py +28 -0
- tooluniverse/test/test_pubchem_tool.py +116 -0
- tooluniverse/test/test_pubtator_tool.py +37 -0
- tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
- tooluniverse/test/test_reactome.py +54 -0
- tooluniverse/test/test_semantic_scholar_tool.py +24 -0
- tooluniverse/test/test_software_tools.py +147 -0
- tooluniverse/test/test_tool_description_optimizer.py +49 -0
- tooluniverse/test/test_tool_finder.py +26 -0
- tooluniverse/test/test_tool_finder_llm.py +252 -0
- tooluniverse/test/test_tools_find.py +195 -0
- tooluniverse/test/test_uniprot_tools.py +74 -0
- tooluniverse/test/test_uspto_tool.py +72 -0
- tooluniverse/test/test_xml_tool.py +113 -0
- tooluniverse/tool_finder_embedding.py +267 -0
- tooluniverse/tool_finder_keyword.py +693 -0
- tooluniverse/tool_finder_llm.py +699 -0
- tooluniverse/tool_graph_web_ui.py +955 -0
- tooluniverse/tool_registry.py +416 -0
- tooluniverse/uniprot_tool.py +155 -0
- tooluniverse/url_tool.py +253 -0
- tooluniverse/uspto_tool.py +240 -0
- tooluniverse/utils.py +369 -41
- tooluniverse/xml_tool.py +369 -0
- tooluniverse-1.0.0.dist-info/METADATA +377 -0
- tooluniverse-1.0.0.dist-info/RECORD +186 -0
- tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
- tooluniverse/generate_mcp_tools.py +0 -113
- tooluniverse/mcp_server.py +0 -3340
- tooluniverse-0.2.0.dist-info/METADATA +0 -139
- tooluniverse-0.2.0.dist-info/RECORD +0 -21
- tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/WHEEL +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/top_level.txt +0 -0
tooluniverse/graphql_tool.py
CHANGED
|
@@ -2,9 +2,9 @@ from graphql import build_schema
|
|
|
2
2
|
from graphql.language import parse
|
|
3
3
|
from graphql.validation import validate
|
|
4
4
|
from .base_tool import BaseTool
|
|
5
|
+
from .tool_registry import register_tool
|
|
5
6
|
import requests
|
|
6
7
|
import copy
|
|
7
|
-
import json
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def validate_query(query_str, schema_str):
|
|
@@ -22,39 +22,49 @@ def validate_query(query_str, schema_str):
|
|
|
22
22
|
return True
|
|
23
23
|
else:
|
|
24
24
|
# Collect and return the validation errors
|
|
25
|
-
error_messages =
|
|
26
|
-
for error in validation_errors)
|
|
25
|
+
error_messages = "\n".join(str(error) for error in validation_errors)
|
|
27
26
|
return f"Query validation errors:\n{error_messages}"
|
|
28
27
|
except Exception as e:
|
|
29
28
|
return f"An error occurred during validation: {str(e)}"
|
|
30
29
|
|
|
30
|
+
|
|
31
31
|
def remove_none_and_empty_values(json_obj):
|
|
32
32
|
"""Remove all key-value pairs where the value is None or an empty list"""
|
|
33
33
|
if isinstance(json_obj, dict):
|
|
34
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
k: remove_none_and_empty_values(v)
|
|
36
|
+
for k, v in json_obj.items()
|
|
37
|
+
if v is not None and v != []
|
|
38
|
+
}
|
|
35
39
|
elif isinstance(json_obj, list):
|
|
36
|
-
return [
|
|
40
|
+
return [
|
|
41
|
+
remove_none_and_empty_values(item)
|
|
42
|
+
for item in json_obj
|
|
43
|
+
if item is not None and item != []
|
|
44
|
+
]
|
|
37
45
|
else:
|
|
38
46
|
return json_obj
|
|
39
47
|
|
|
48
|
+
|
|
40
49
|
def execute_query(endpoint_url, query, variables=None):
|
|
41
50
|
response = requests.post(
|
|
42
|
-
endpoint_url, json={
|
|
51
|
+
endpoint_url, json={"query": query, "variables": variables}
|
|
52
|
+
)
|
|
43
53
|
try:
|
|
44
54
|
result = response.json()
|
|
45
55
|
# result = json.dumps(result, ensure_ascii=False)
|
|
46
56
|
result = remove_none_and_empty_values(result)
|
|
47
57
|
# Check if the response contains errors
|
|
48
|
-
if
|
|
49
|
-
print("Invalid Query: ", result[
|
|
58
|
+
if "errors" in result:
|
|
59
|
+
print("Invalid Query: ", result["errors"])
|
|
50
60
|
return None
|
|
51
|
-
|
|
52
|
-
elif not result.get(
|
|
61
|
+
# Check if the data field is empty
|
|
62
|
+
elif not result.get("data") or all(not v for v in result["data"].values()):
|
|
53
63
|
print("No data returned")
|
|
54
64
|
return None
|
|
55
65
|
else:
|
|
56
66
|
return result
|
|
57
|
-
except requests.exceptions.JSONDecodeError
|
|
67
|
+
except requests.exceptions.JSONDecodeError:
|
|
58
68
|
print("JSONDecodeError: Could not decode the response as JSON")
|
|
59
69
|
return None
|
|
60
70
|
|
|
@@ -63,60 +73,172 @@ class GraphQLTool(BaseTool):
|
|
|
63
73
|
def __init__(self, tool_config, endpoint_url):
|
|
64
74
|
super().__init__(tool_config)
|
|
65
75
|
self.endpoint_url = endpoint_url
|
|
66
|
-
self.query_schema = tool_config[
|
|
67
|
-
self.parameters = tool_config[
|
|
76
|
+
self.query_schema = tool_config["query_schema"]
|
|
77
|
+
self.parameters = tool_config["parameter"]["properties"]
|
|
68
78
|
self.default_size = 5
|
|
69
79
|
|
|
70
80
|
def run(self, arguments):
|
|
71
81
|
arguments = copy.deepcopy(arguments)
|
|
72
|
-
if
|
|
73
|
-
arguments[
|
|
74
|
-
return execute_query(
|
|
82
|
+
if "size" in self.parameters and "size" not in arguments:
|
|
83
|
+
arguments["size"] = 5
|
|
84
|
+
return execute_query(
|
|
85
|
+
endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments
|
|
86
|
+
)
|
|
75
87
|
|
|
76
88
|
|
|
89
|
+
@register_tool("OpenTarget")
|
|
77
90
|
class OpentargetTool(GraphQLTool):
|
|
78
91
|
def __init__(self, tool_config):
|
|
79
|
-
endpoint_url =
|
|
92
|
+
endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
|
|
80
93
|
super().__init__(tool_config, endpoint_url)
|
|
81
|
-
|
|
94
|
+
|
|
82
95
|
def run(self, arguments):
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
# First try without modifying '-'
|
|
97
|
+
result = super().run(arguments)
|
|
98
|
+
|
|
99
|
+
# If no results, try with '-' replaced by ' '
|
|
100
|
+
if result is None:
|
|
101
|
+
if "drugName" in arguments and isinstance(arguments["drugName"], str):
|
|
102
|
+
arguments["drugName"] = arguments["drugName"].split("-")[0]
|
|
103
|
+
modified_arguments = copy.deepcopy(arguments)
|
|
104
|
+
for each_arg, arg_value in modified_arguments.items():
|
|
105
|
+
if isinstance(arg_value, str) and "-" in arg_value:
|
|
106
|
+
modified_arguments[each_arg] = arg_value.replace("-", " ")
|
|
107
|
+
result = super().run(modified_arguments)
|
|
108
|
+
return result
|
|
89
109
|
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@register_tool("OpentargetToolDrugNameMatch")
|
|
90
114
|
class OpentargetToolDrugNameMatch(GraphQLTool):
|
|
91
115
|
def __init__(self, tool_config, drug_generic_tool=None):
|
|
92
|
-
endpoint_url =
|
|
116
|
+
endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
|
|
93
117
|
self.drug_generic_tool = drug_generic_tool
|
|
94
|
-
self.possible_drug_name_args = [
|
|
118
|
+
self.possible_drug_name_args = ["drugName"]
|
|
95
119
|
super().__init__(tool_config, endpoint_url)
|
|
96
120
|
|
|
97
121
|
def run(self, arguments):
|
|
98
122
|
arguments = copy.deepcopy(arguments)
|
|
99
|
-
results = execute_query(
|
|
123
|
+
results = execute_query(
|
|
124
|
+
endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments
|
|
125
|
+
)
|
|
100
126
|
if results is None:
|
|
101
|
-
print(
|
|
127
|
+
print(
|
|
128
|
+
"No results found for the drug brand name. Trying with the generic name."
|
|
129
|
+
)
|
|
102
130
|
name_arguments = {}
|
|
103
131
|
for each_args in self.possible_drug_name_args:
|
|
104
132
|
if each_args in arguments:
|
|
105
|
-
name_arguments[
|
|
133
|
+
name_arguments["drug_name"] = arguments[each_args]
|
|
106
134
|
break
|
|
107
|
-
if len(name_arguments)==0:
|
|
135
|
+
if len(name_arguments) == 0:
|
|
108
136
|
print("No drug name found in the arguments.")
|
|
109
137
|
return None
|
|
110
138
|
drug_name_results = self.drug_generic_tool.run(name_arguments)
|
|
111
|
-
if
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
139
|
+
if (
|
|
140
|
+
drug_name_results is not None
|
|
141
|
+
and "openfda.generic_name" in drug_name_results
|
|
142
|
+
):
|
|
143
|
+
arguments[each_args] = drug_name_results["openfda.generic_name"]
|
|
144
|
+
print(
|
|
145
|
+
"Found generic name. Trying with the generic name: ",
|
|
146
|
+
arguments[each_args],
|
|
147
|
+
)
|
|
148
|
+
results = execute_query(
|
|
149
|
+
endpoint_url=self.endpoint_url,
|
|
150
|
+
query=self.query_schema,
|
|
151
|
+
variables=arguments,
|
|
152
|
+
)
|
|
115
153
|
return results
|
|
116
|
-
|
|
117
154
|
|
|
118
155
|
|
|
156
|
+
@register_tool("OpenTargetGenetics")
|
|
119
157
|
class OpentargetGeneticsTool(GraphQLTool):
|
|
120
158
|
def __init__(self, tool_config):
|
|
121
|
-
endpoint_url =
|
|
159
|
+
endpoint_url = "https://api.genetics.opentargets.org/graphql"
|
|
122
160
|
super().__init__(tool_config, endpoint_url)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@register_tool("DiseaseTargetScoreTool")
|
|
164
|
+
class DiseaseTargetScoreTool(GraphQLTool):
|
|
165
|
+
"""Tool to extract disease-target association scores from specific data sources"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, tool_config, datasource_id=None):
|
|
168
|
+
endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
|
|
169
|
+
# Get datasource_id from config if not provided as parameter
|
|
170
|
+
self.datasource_id = datasource_id or tool_config.get("datasource_id")
|
|
171
|
+
super().__init__(tool_config, endpoint_url)
|
|
172
|
+
|
|
173
|
+
def run(self, arguments):
|
|
174
|
+
"""
|
|
175
|
+
Extract disease-target scores for a specific datasource
|
|
176
|
+
Arguments should contain: efoId, datasourceId (optional), pageSize (optional)
|
|
177
|
+
"""
|
|
178
|
+
arguments = copy.deepcopy(arguments)
|
|
179
|
+
efo_id = arguments.get("efoId")
|
|
180
|
+
datasource_id = arguments.get("datasourceId", self.datasource_id)
|
|
181
|
+
page_size = arguments.get("pageSize", 100)
|
|
182
|
+
|
|
183
|
+
if not efo_id:
|
|
184
|
+
return {"error": "efoId is required"}
|
|
185
|
+
if not datasource_id:
|
|
186
|
+
return {"error": "datasourceId is required"}
|
|
187
|
+
|
|
188
|
+
results = []
|
|
189
|
+
page_index = 0
|
|
190
|
+
total_fetched = 0
|
|
191
|
+
total_count = None
|
|
192
|
+
disease_info = None
|
|
193
|
+
|
|
194
|
+
while True:
|
|
195
|
+
variables = {"efoId": efo_id, "index": page_index, "size": page_size}
|
|
196
|
+
|
|
197
|
+
response_data = execute_query(
|
|
198
|
+
self.endpoint_url, self.query_schema, variables
|
|
199
|
+
)
|
|
200
|
+
if not response_data or "data" not in response_data:
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
disease_data = response_data["data"]["disease"]
|
|
204
|
+
if not disease_data:
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
if disease_info is None:
|
|
208
|
+
disease_info = {
|
|
209
|
+
"disease_id": disease_data["id"],
|
|
210
|
+
"disease_name": disease_data["name"],
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
rows = disease_data["associatedTargets"]["rows"]
|
|
214
|
+
if total_count is None:
|
|
215
|
+
total_count = disease_data["associatedTargets"]["count"]
|
|
216
|
+
|
|
217
|
+
for row in rows:
|
|
218
|
+
symbol = row["target"]["approvedSymbol"]
|
|
219
|
+
target_id = row["target"]["id"]
|
|
220
|
+
score_entry = next(
|
|
221
|
+
(ds for ds in row["datasourceScores"] if ds["id"] == datasource_id),
|
|
222
|
+
None,
|
|
223
|
+
)
|
|
224
|
+
if score_entry:
|
|
225
|
+
results.append(
|
|
226
|
+
{
|
|
227
|
+
"target_symbol": symbol,
|
|
228
|
+
"target_id": target_id,
|
|
229
|
+
"datasource": datasource_id,
|
|
230
|
+
"score": score_entry["score"],
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
total_fetched += len(rows)
|
|
235
|
+
if total_fetched >= total_count or len(rows) == 0:
|
|
236
|
+
break
|
|
237
|
+
page_index += 1
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
"disease_info": disease_info,
|
|
241
|
+
"datasource": datasource_id,
|
|
242
|
+
"total_targets_with_scores": len(results),
|
|
243
|
+
"target_scores": results,
|
|
244
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from .base_tool import BaseTool
|
|
4
|
+
from .tool_registry import register_tool
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GWASRESTTool(BaseTool):
|
|
8
|
+
"""Base class for GWAS Catalog REST API tools."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, tool_config):
|
|
11
|
+
super().__init__(tool_config)
|
|
12
|
+
self.base_url = "https://www.ebi.ac.uk/gwas/rest/api"
|
|
13
|
+
self.endpoint = "" # Will be set by subclasses
|
|
14
|
+
|
|
15
|
+
def _make_request(
|
|
16
|
+
self, endpoint: str, params: Optional[Dict] = None
|
|
17
|
+
) -> Dict[str, Any]:
|
|
18
|
+
"""Make a request to the GWAS Catalog API."""
|
|
19
|
+
url = f"{self.base_url}{endpoint}"
|
|
20
|
+
try:
|
|
21
|
+
response = requests.get(url, params=params, timeout=30)
|
|
22
|
+
response.raise_for_status()
|
|
23
|
+
return response.json()
|
|
24
|
+
except requests.exceptions.RequestException as e:
|
|
25
|
+
return {"error": f"Request failed: {str(e)}"}
|
|
26
|
+
|
|
27
|
+
def _extract_embedded_data(
|
|
28
|
+
self, data: Dict[str, Any], data_type: str
|
|
29
|
+
) -> Dict[str, Any]:
|
|
30
|
+
"""Extract data from the _embedded structure and add metadata."""
|
|
31
|
+
if "error" in data:
|
|
32
|
+
return data
|
|
33
|
+
|
|
34
|
+
result: Dict[str, Any] = {"data": [], "metadata": {}}
|
|
35
|
+
metadata: Dict[str, Any] = {}
|
|
36
|
+
|
|
37
|
+
# Extract the main data from _embedded
|
|
38
|
+
if "_embedded" in data and data_type in data["_embedded"]:
|
|
39
|
+
result["data"] = data["_embedded"][data_type]
|
|
40
|
+
|
|
41
|
+
# Extract pagination metadata
|
|
42
|
+
if "page" in data:
|
|
43
|
+
metadata["pagination"] = data["page"]
|
|
44
|
+
|
|
45
|
+
# Extract links metadata
|
|
46
|
+
if "_links" in data:
|
|
47
|
+
metadata["links"] = data["_links"]
|
|
48
|
+
|
|
49
|
+
if metadata:
|
|
50
|
+
result["metadata"] = metadata
|
|
51
|
+
|
|
52
|
+
# If no _embedded structure, return the original data
|
|
53
|
+
if not result["data"]:
|
|
54
|
+
result["data"] = data
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
59
|
+
"""Execute the tool with given arguments."""
|
|
60
|
+
return self._make_request(self.endpoint, arguments)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@register_tool("GWASAssociationSearch")
|
|
64
|
+
class GWASAssociationSearch(GWASRESTTool):
|
|
65
|
+
"""Search for GWAS associations by various criteria."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, tool_config):
|
|
68
|
+
super().__init__(tool_config)
|
|
69
|
+
self.endpoint = "/v2/associations"
|
|
70
|
+
|
|
71
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
72
|
+
"""Search for associations with optional filters."""
|
|
73
|
+
params = {}
|
|
74
|
+
|
|
75
|
+
# Handle various search parameters based on examples
|
|
76
|
+
if "efo_trait" in arguments:
|
|
77
|
+
params["efo_trait"] = arguments["efo_trait"]
|
|
78
|
+
if "rs_id" in arguments:
|
|
79
|
+
params["rs_id"] = arguments["rs_id"]
|
|
80
|
+
if "accession_id" in arguments:
|
|
81
|
+
params["accession_id"] = arguments["accession_id"]
|
|
82
|
+
if "sort" in arguments:
|
|
83
|
+
params["sort"] = arguments["sort"]
|
|
84
|
+
if "direction" in arguments:
|
|
85
|
+
params["direction"] = arguments["direction"]
|
|
86
|
+
if "size" in arguments:
|
|
87
|
+
params["size"] = arguments["size"]
|
|
88
|
+
if "page" in arguments:
|
|
89
|
+
params["page"] = arguments["page"]
|
|
90
|
+
|
|
91
|
+
data = self._make_request(self.endpoint, params)
|
|
92
|
+
return self._extract_embedded_data(data, "associations")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@register_tool("GWASStudySearch")
|
|
96
|
+
class GWASStudySearch(GWASRESTTool):
|
|
97
|
+
"""Search for GWAS studies by various criteria."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, tool_config):
|
|
100
|
+
super().__init__(tool_config)
|
|
101
|
+
self.endpoint = "/v2/studies"
|
|
102
|
+
|
|
103
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
104
|
+
"""Search for studies with optional filters."""
|
|
105
|
+
params = {}
|
|
106
|
+
|
|
107
|
+
if "efo_trait" in arguments:
|
|
108
|
+
params["efo_trait"] = arguments["efo_trait"]
|
|
109
|
+
if "disease_trait" in arguments:
|
|
110
|
+
params["disease_trait"] = arguments["disease_trait"]
|
|
111
|
+
if "cohort" in arguments:
|
|
112
|
+
params["cohort"] = arguments["cohort"]
|
|
113
|
+
if "gxe" in arguments:
|
|
114
|
+
params["gxe"] = arguments["gxe"]
|
|
115
|
+
if "full_pvalue_set" in arguments:
|
|
116
|
+
params["full_pvalue_set"] = arguments["full_pvalue_set"]
|
|
117
|
+
if "size" in arguments:
|
|
118
|
+
params["size"] = arguments["size"]
|
|
119
|
+
if "page" in arguments:
|
|
120
|
+
params["page"] = arguments["page"]
|
|
121
|
+
|
|
122
|
+
data = self._make_request(self.endpoint, params)
|
|
123
|
+
return self._extract_embedded_data(data, "studies")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@register_tool("GWASSNPSearch")
|
|
127
|
+
class GWASSNPSearch(GWASRESTTool):
|
|
128
|
+
"""Search for GWAS single nucleotide polymorphisms (SNPs)."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, tool_config):
|
|
131
|
+
super().__init__(tool_config)
|
|
132
|
+
self.endpoint = "/v2/single-nucleotide-polymorphisms"
|
|
133
|
+
|
|
134
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
135
|
+
"""Search for SNPs with optional filters."""
|
|
136
|
+
params = {}
|
|
137
|
+
|
|
138
|
+
if "rs_id" in arguments:
|
|
139
|
+
params["rs_id"] = arguments["rs_id"]
|
|
140
|
+
if "mapped_gene" in arguments:
|
|
141
|
+
params["mapped_gene"] = arguments["mapped_gene"]
|
|
142
|
+
if "size" in arguments:
|
|
143
|
+
params["size"] = arguments["size"]
|
|
144
|
+
if "page" in arguments:
|
|
145
|
+
params["page"] = arguments["page"]
|
|
146
|
+
|
|
147
|
+
data = self._make_request(self.endpoint, params)
|
|
148
|
+
return self._extract_embedded_data(data, "snps")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# Get by ID tools
|
|
152
|
+
@register_tool("GWASAssociationByID")
|
|
153
|
+
class GWASAssociationByID(GWASRESTTool):
|
|
154
|
+
"""Get a specific GWAS association by its ID."""
|
|
155
|
+
|
|
156
|
+
def __init__(self, tool_config):
|
|
157
|
+
super().__init__(tool_config)
|
|
158
|
+
self.endpoint = "/v2/associations"
|
|
159
|
+
|
|
160
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
161
|
+
"""Get association by ID."""
|
|
162
|
+
if "association_id" not in arguments:
|
|
163
|
+
return {"error": "association_id is required"}
|
|
164
|
+
|
|
165
|
+
association_id = arguments["association_id"]
|
|
166
|
+
return self._make_request(f"{self.endpoint}/{association_id}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@register_tool("GWASStudyByID")
|
|
170
|
+
class GWASStudyByID(GWASRESTTool):
|
|
171
|
+
"""Get a specific GWAS study by its ID."""
|
|
172
|
+
|
|
173
|
+
def __init__(self, tool_config):
|
|
174
|
+
super().__init__(tool_config)
|
|
175
|
+
self.endpoint = "/v2/studies"
|
|
176
|
+
|
|
177
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
178
|
+
"""Get study by ID."""
|
|
179
|
+
if "study_id" not in arguments:
|
|
180
|
+
return {"error": "study_id is required"}
|
|
181
|
+
|
|
182
|
+
study_id = arguments["study_id"]
|
|
183
|
+
return self._make_request(f"{self.endpoint}/{study_id}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@register_tool("GWASSNPByID")
|
|
187
|
+
class GWASSNPByID(GWASRESTTool):
|
|
188
|
+
"""Get a specific GWAS SNP by its rs ID."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, tool_config):
|
|
191
|
+
super().__init__(tool_config)
|
|
192
|
+
self.endpoint = "/v2/single-nucleotide-polymorphisms"
|
|
193
|
+
|
|
194
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
195
|
+
"""Get SNP by rs ID."""
|
|
196
|
+
if "rs_id" not in arguments:
|
|
197
|
+
return {"error": "rs_id is required"}
|
|
198
|
+
|
|
199
|
+
rs_id = arguments["rs_id"]
|
|
200
|
+
return self._make_request(f"{self.endpoint}/{rs_id}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Specialized search tools based on common use cases from examples
|
|
204
|
+
@register_tool("GWASVariantsForTrait")
|
|
205
|
+
class GWASVariantsForTrait(GWASRESTTool):
|
|
206
|
+
"""Get all variants associated with a specific trait."""
|
|
207
|
+
|
|
208
|
+
def __init__(self, tool_config):
|
|
209
|
+
super().__init__(tool_config)
|
|
210
|
+
self.endpoint = "/v2/associations"
|
|
211
|
+
|
|
212
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
213
|
+
"""Get variants for a trait with pagination support."""
|
|
214
|
+
if "efo_trait" not in arguments:
|
|
215
|
+
return {"error": "efo_trait is required"}
|
|
216
|
+
|
|
217
|
+
params = {
|
|
218
|
+
"efo_trait": arguments["efo_trait"],
|
|
219
|
+
"size": arguments.get("size", 200),
|
|
220
|
+
"page": arguments.get("page", 0),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
data = self._make_request(self.endpoint, params)
|
|
224
|
+
return self._extract_embedded_data(data, "associations")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@register_tool("GWASAssociationsForTrait")
|
|
228
|
+
class GWASAssociationsForTrait(GWASRESTTool):
|
|
229
|
+
"""Get all associations for a specific trait, sorted by p-value."""
|
|
230
|
+
|
|
231
|
+
def __init__(self, tool_config):
|
|
232
|
+
super().__init__(tool_config)
|
|
233
|
+
self.endpoint = "/v2/associations"
|
|
234
|
+
|
|
235
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
236
|
+
"""Get associations for a trait, sorted by significance."""
|
|
237
|
+
if "efo_trait" not in arguments:
|
|
238
|
+
return {"error": "efo_trait is required"}
|
|
239
|
+
|
|
240
|
+
params = {
|
|
241
|
+
"efo_trait": arguments["efo_trait"],
|
|
242
|
+
"sort": "p_value",
|
|
243
|
+
"direction": "asc",
|
|
244
|
+
"size": arguments.get("size", 40),
|
|
245
|
+
"page": arguments.get("page", 0),
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
data = self._make_request(self.endpoint, params)
|
|
249
|
+
return self._extract_embedded_data(data, "associations")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@register_tool("GWASAssociationsForSNP")
|
|
253
|
+
class GWASAssociationsForSNP(GWASRESTTool):
|
|
254
|
+
"""Get all associations for a specific SNP."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, tool_config):
|
|
257
|
+
super().__init__(tool_config)
|
|
258
|
+
self.endpoint = "/v2/associations"
|
|
259
|
+
|
|
260
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
261
|
+
"""Get associations for a SNP."""
|
|
262
|
+
if "rs_id" not in arguments:
|
|
263
|
+
return {"error": "rs_id is required"}
|
|
264
|
+
|
|
265
|
+
params = {
|
|
266
|
+
"rs_id": arguments["rs_id"],
|
|
267
|
+
"size": arguments.get("size", 200),
|
|
268
|
+
"page": arguments.get("page", 0),
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if "sort" in arguments:
|
|
272
|
+
params["sort"] = arguments["sort"]
|
|
273
|
+
if "direction" in arguments:
|
|
274
|
+
params["direction"] = arguments["direction"]
|
|
275
|
+
|
|
276
|
+
data = self._make_request(self.endpoint, params)
|
|
277
|
+
return self._extract_embedded_data(data, "associations")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@register_tool("GWASStudiesForTrait")
|
|
281
|
+
class GWASStudiesForTrait(GWASRESTTool):
|
|
282
|
+
"""Get studies for a specific trait with optional filters."""
|
|
283
|
+
|
|
284
|
+
def __init__(self, tool_config):
|
|
285
|
+
super().__init__(tool_config)
|
|
286
|
+
self.endpoint = "/v2/studies"
|
|
287
|
+
|
|
288
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
289
|
+
"""Get studies for a trait with optional filters."""
|
|
290
|
+
if "efo_trait" not in arguments and "disease_trait" not in arguments:
|
|
291
|
+
return {"error": "efo_trait or disease_trait is required"}
|
|
292
|
+
|
|
293
|
+
params = {
|
|
294
|
+
"size": arguments.get("size", 200),
|
|
295
|
+
"page": arguments.get("page", 0),
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if "efo_trait" in arguments:
|
|
299
|
+
params["efo_trait"] = arguments["efo_trait"]
|
|
300
|
+
if "disease_trait" in arguments:
|
|
301
|
+
params["disease_trait"] = arguments["disease_trait"]
|
|
302
|
+
if "cohort" in arguments:
|
|
303
|
+
params["cohort"] = arguments["cohort"]
|
|
304
|
+
if "gxe" in arguments:
|
|
305
|
+
params["gxe"] = arguments["gxe"]
|
|
306
|
+
if "full_pvalue_set" in arguments:
|
|
307
|
+
params["full_pvalue_set"] = arguments["full_pvalue_set"]
|
|
308
|
+
|
|
309
|
+
data = self._make_request(self.endpoint, params)
|
|
310
|
+
return self._extract_embedded_data(data, "studies")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@register_tool("GWASSNPsForGene")
|
|
314
|
+
class GWASSNPsForGene(GWASRESTTool):
|
|
315
|
+
"""Get SNPs mapped to a specific gene."""
|
|
316
|
+
|
|
317
|
+
def __init__(self, tool_config):
|
|
318
|
+
super().__init__(tool_config)
|
|
319
|
+
self.endpoint = "/v2/single-nucleotide-polymorphisms"
|
|
320
|
+
|
|
321
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
322
|
+
"""Get SNPs for a gene."""
|
|
323
|
+
if "mapped_gene" not in arguments:
|
|
324
|
+
return {"error": "mapped_gene is required"}
|
|
325
|
+
|
|
326
|
+
params = {
|
|
327
|
+
"mapped_gene": arguments["mapped_gene"],
|
|
328
|
+
"size": arguments.get("size", 10000),
|
|
329
|
+
"page": arguments.get("page", 0),
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
data = self._make_request(self.endpoint, params)
|
|
333
|
+
return self._extract_embedded_data(data, "snps")
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@register_tool("GWASAssociationsForStudy")
|
|
337
|
+
class GWASAssociationsForStudy(GWASRESTTool):
|
|
338
|
+
"""Get all associations for a specific study."""
|
|
339
|
+
|
|
340
|
+
def __init__(self, tool_config):
|
|
341
|
+
super().__init__(tool_config)
|
|
342
|
+
self.endpoint = "/v2/associations"
|
|
343
|
+
|
|
344
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
345
|
+
"""Get associations for a study."""
|
|
346
|
+
if "accession_id" not in arguments:
|
|
347
|
+
return {"error": "accession_id is required"}
|
|
348
|
+
|
|
349
|
+
params = {
|
|
350
|
+
"accession_id": arguments["accession_id"],
|
|
351
|
+
"sort": "p_value",
|
|
352
|
+
"direction": "asc",
|
|
353
|
+
"size": arguments.get("size", 200),
|
|
354
|
+
"page": arguments.get("page", 0),
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
data = self._make_request(self.endpoint, params)
|
|
358
|
+
return self._extract_embedded_data(data, "associations")
|