tooluniverse 1.0.10__py3-none-any.whl → 1.0.11__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.

Files changed (150) hide show
  1. tooluniverse/__init__.py +57 -1
  2. tooluniverse/blast_tool.py +132 -0
  3. tooluniverse/boltz_tool.py +2 -2
  4. tooluniverse/cbioportal_tool.py +42 -0
  5. tooluniverse/clinvar_tool.py +268 -74
  6. tooluniverse/compose_scripts/tool_discover.py +1941 -443
  7. tooluniverse/data/agentic_tools.json +0 -370
  8. tooluniverse/data/alphafold_tools.json +6 -6
  9. tooluniverse/data/blast_tools.json +112 -0
  10. tooluniverse/data/cbioportal_tools.json +87 -0
  11. tooluniverse/data/clinvar_tools.json +235 -0
  12. tooluniverse/data/compose_tools.json +0 -89
  13. tooluniverse/data/dbsnp_tools.json +275 -0
  14. tooluniverse/data/emdb_tools.json +61 -0
  15. tooluniverse/data/ensembl_tools.json +259 -0
  16. tooluniverse/data/file_download_tools.json +275 -0
  17. tooluniverse/data/geo_tools.json +200 -48
  18. tooluniverse/data/gnomad_tools.json +109 -0
  19. tooluniverse/data/gtopdb_tools.json +68 -0
  20. tooluniverse/data/gwas_tools.json +32 -0
  21. tooluniverse/data/interpro_tools.json +199 -0
  22. tooluniverse/data/jaspar_tools.json +70 -0
  23. tooluniverse/data/kegg_tools.json +356 -0
  24. tooluniverse/data/mpd_tools.json +87 -0
  25. tooluniverse/data/ols_tools.json +314 -0
  26. tooluniverse/data/package_discovery_tools.json +64 -0
  27. tooluniverse/data/packages/categorized_tools.txt +0 -1
  28. tooluniverse/data/packages/machine_learning_tools.json +0 -47
  29. tooluniverse/data/paleobiology_tools.json +91 -0
  30. tooluniverse/data/pride_tools.json +62 -0
  31. tooluniverse/data/pypi_package_inspector_tools.json +158 -0
  32. tooluniverse/data/python_executor_tools.json +341 -0
  33. tooluniverse/data/regulomedb_tools.json +50 -0
  34. tooluniverse/data/remap_tools.json +89 -0
  35. tooluniverse/data/screen_tools.json +89 -0
  36. tooluniverse/data/tool_discovery_agents.json +428 -0
  37. tooluniverse/data/tool_discovery_agents.json.backup +1343 -0
  38. tooluniverse/data/uniprot_tools.json +77 -0
  39. tooluniverse/data/web_search_tools.json +250 -0
  40. tooluniverse/data/worms_tools.json +55 -0
  41. tooluniverse/dbsnp_tool.py +196 -58
  42. tooluniverse/default_config.py +35 -2
  43. tooluniverse/emdb_tool.py +30 -0
  44. tooluniverse/ensembl_tool.py +140 -47
  45. tooluniverse/execute_function.py +74 -14
  46. tooluniverse/file_download_tool.py +269 -0
  47. tooluniverse/geo_tool.py +81 -28
  48. tooluniverse/gnomad_tool.py +100 -52
  49. tooluniverse/gtopdb_tool.py +41 -0
  50. tooluniverse/interpro_tool.py +72 -0
  51. tooluniverse/jaspar_tool.py +30 -0
  52. tooluniverse/kegg_tool.py +230 -0
  53. tooluniverse/mpd_tool.py +42 -0
  54. tooluniverse/ncbi_eutils_tool.py +96 -0
  55. tooluniverse/ols_tool.py +435 -0
  56. tooluniverse/package_discovery_tool.py +217 -0
  57. tooluniverse/paleobiology_tool.py +30 -0
  58. tooluniverse/pride_tool.py +30 -0
  59. tooluniverse/pypi_package_inspector_tool.py +593 -0
  60. tooluniverse/python_executor_tool.py +711 -0
  61. tooluniverse/regulomedb_tool.py +30 -0
  62. tooluniverse/remap_tool.py +44 -0
  63. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +1 -1
  64. tooluniverse/screen_tool.py +44 -0
  65. tooluniverse/smcp_server.py +3 -3
  66. tooluniverse/tool_finder_embedding.py +3 -1
  67. tooluniverse/tool_finder_keyword.py +3 -1
  68. tooluniverse/tool_finder_llm.py +6 -2
  69. tooluniverse/tools/{UCSC_get_genes_by_region.py → BLAST_nucleotide_search.py} +22 -26
  70. tooluniverse/tools/BLAST_protein_search.py +63 -0
  71. tooluniverse/tools/ClinVar_search_variants.py +26 -15
  72. tooluniverse/tools/CodeQualityAnalyzer.py +3 -3
  73. tooluniverse/tools/EMDB_get_structure.py +46 -0
  74. tooluniverse/tools/GtoPdb_get_targets.py +52 -0
  75. tooluniverse/tools/InterPro_get_domain_details.py +46 -0
  76. tooluniverse/tools/InterPro_get_protein_domains.py +49 -0
  77. tooluniverse/tools/InterPro_search_domains.py +52 -0
  78. tooluniverse/tools/JASPAR_get_transcription_factors.py +52 -0
  79. tooluniverse/tools/MPD_get_phenotype_data.py +59 -0
  80. tooluniverse/tools/PRIDE_search_proteomics.py +52 -0
  81. tooluniverse/tools/PackageAnalyzer.py +55 -0
  82. tooluniverse/tools/Paleobiology_get_fossils.py +52 -0
  83. tooluniverse/tools/PyPIPackageInspector.py +59 -0
  84. tooluniverse/tools/ReMap_get_transcription_factor_binding.py +59 -0
  85. tooluniverse/tools/ReferenceInfoAnalyzer.py +55 -0
  86. tooluniverse/tools/RegulomeDB_query_variant.py +46 -0
  87. tooluniverse/tools/SCREEN_get_regulatory_elements.py +59 -0
  88. tooluniverse/tools/{ArgumentDescriptionOptimizer.py → TestResultsAnalyzer.py} +13 -13
  89. tooluniverse/tools/ToolDiscover.py +11 -11
  90. tooluniverse/tools/UniProt_id_mapping.py +63 -0
  91. tooluniverse/tools/UniProt_search.py +63 -0
  92. tooluniverse/tools/UnifiedToolGenerator.py +59 -0
  93. tooluniverse/tools/WoRMS_search_species.py +49 -0
  94. tooluniverse/tools/XMLToolOptimizer.py +55 -0
  95. tooluniverse/tools/__init__.py +119 -29
  96. tooluniverse/tools/alphafold_get_annotations.py +3 -3
  97. tooluniverse/tools/alphafold_get_prediction.py +3 -3
  98. tooluniverse/tools/alphafold_get_summary.py +3 -3
  99. tooluniverse/tools/cBioPortal_get_cancer_studies.py +46 -0
  100. tooluniverse/tools/cBioPortal_get_mutations.py +52 -0
  101. tooluniverse/tools/{gnomAD_query_variant.py → clinvar_get_clinical_significance.py} +8 -11
  102. tooluniverse/tools/clinvar_get_variant_details.py +49 -0
  103. tooluniverse/tools/dbSNP_get_variant_by_rsid.py +7 -7
  104. tooluniverse/tools/dbsnp_get_frequencies.py +46 -0
  105. tooluniverse/tools/dbsnp_search_by_gene.py +52 -0
  106. tooluniverse/tools/download_binary_file.py +66 -0
  107. tooluniverse/tools/download_file.py +71 -0
  108. tooluniverse/tools/download_text_content.py +55 -0
  109. tooluniverse/tools/dynamic_package_discovery.py +59 -0
  110. tooluniverse/tools/ensembl_get_sequence.py +52 -0
  111. tooluniverse/tools/{Ensembl_lookup_gene_by_symbol.py → ensembl_get_variants.py} +11 -11
  112. tooluniverse/tools/ensembl_lookup_gene.py +46 -0
  113. tooluniverse/tools/geo_get_dataset_info.py +46 -0
  114. tooluniverse/tools/geo_get_sample_info.py +46 -0
  115. tooluniverse/tools/geo_search_datasets.py +67 -0
  116. tooluniverse/tools/gnomad_get_gene_constraints.py +49 -0
  117. tooluniverse/tools/kegg_find_genes.py +52 -0
  118. tooluniverse/tools/kegg_get_gene_info.py +46 -0
  119. tooluniverse/tools/kegg_get_pathway_info.py +46 -0
  120. tooluniverse/tools/kegg_list_organisms.py +44 -0
  121. tooluniverse/tools/kegg_search_pathway.py +46 -0
  122. tooluniverse/tools/ols_find_similar_terms.py +63 -0
  123. tooluniverse/tools/{get_hyperopt_info.py → ols_get_ontology_info.py} +13 -10
  124. tooluniverse/tools/ols_get_term_ancestors.py +67 -0
  125. tooluniverse/tools/ols_get_term_children.py +67 -0
  126. tooluniverse/tools/{TestCaseGenerator.py → ols_get_term_info.py} +12 -9
  127. tooluniverse/tools/{CodeOptimizer.py → ols_search_ontologies.py} +22 -14
  128. tooluniverse/tools/ols_search_terms.py +71 -0
  129. tooluniverse/tools/python_code_executor.py +79 -0
  130. tooluniverse/tools/python_script_runner.py +79 -0
  131. tooluniverse/tools/web_api_documentation_search.py +63 -0
  132. tooluniverse/tools/web_search.py +71 -0
  133. tooluniverse/uniprot_tool.py +219 -16
  134. tooluniverse/url_tool.py +18 -0
  135. tooluniverse/utils.py +2 -2
  136. tooluniverse/web_search_tool.py +229 -0
  137. tooluniverse/worms_tool.py +64 -0
  138. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/METADATA +3 -2
  139. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/RECORD +143 -54
  140. tooluniverse/data/genomics_tools.json +0 -174
  141. tooluniverse/tools/ToolDescriptionOptimizer.py +0 -67
  142. tooluniverse/tools/ToolImplementationGenerator.py +0 -67
  143. tooluniverse/tools/ToolOptimizer.py +0 -59
  144. tooluniverse/tools/ToolSpecificationGenerator.py +0 -67
  145. tooluniverse/tools/ToolSpecificationOptimizer.py +0 -63
  146. tooluniverse/ucsc_tool.py +0 -60
  147. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/WHEEL +0 -0
  148. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/entry_points.txt +0 -0
  149. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/licenses/LICENSE +0 -0
  150. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,269 @@
1
+ """
2
+ File download tool - A curl-like tool for downloading files from URLs.
3
+ Supports Windows, Mac, and Linux platforms.
4
+ """
5
+
6
+ import requests
7
+ import os
8
+ import tempfile
9
+ from urllib.parse import urlparse
10
+ from typing import Dict, Any
11
+ from .base_tool import BaseTool
12
+ from .tool_registry import register_tool
13
+
14
+
15
+ @register_tool("FileDownloadTool")
16
+ class FileDownloadTool(BaseTool):
17
+ """
18
+ Download files from HTTP/HTTPS URLs - similar to curl.
19
+
20
+ Supports:
21
+ - Direct file downloads to specified or temporary locations
22
+ - Binary and text file handling
23
+ - Progress tracking (optional)
24
+ - Cross-platform (Windows, Mac, Linux)
25
+
26
+ Expects: {"url": "https://...", "output_path": "/path/to/save"}
27
+ Optional: {"timeout": seconds, "return_content": bool}
28
+ Returns: {"file_path": "...", "file_size": bytes} or {"error": "..."}
29
+ """
30
+
31
+ def __init__(self, tool_config):
32
+ super().__init__(tool_config)
33
+ fields = tool_config.get("fields", {})
34
+ self.return_key = fields.get("return_key", "file_path")
35
+
36
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
37
+ """
38
+ Download a file from a URL.
39
+
40
+ Args:
41
+ arguments: Dictionary containing:
42
+ - url (str): URL to download from
43
+ - output_path (str, optional): Path to save the file
44
+ - timeout (int, optional): Request timeout (default: 30)
45
+ - return_content (bool): Return as text (default: False)
46
+ - chunk_size (int, optional): Chunk size (default: 8192)
47
+ - follow_redirects (bool): Follow redirects (default: True)
48
+ Returns:
49
+ Dict with file_path and file_size, or content, or error
50
+ """
51
+ url = arguments.get("url")
52
+ if not url:
53
+ return {"error": "Parameter 'url' is required."}
54
+
55
+ if not (url.startswith("http://") or url.startswith("https://")):
56
+ return {"error": "URL must start with http:// or https://"}
57
+
58
+ # Parse parameters
59
+ output_path = arguments.get("output_path")
60
+ timeout = arguments.get("timeout", 30)
61
+ return_content = arguments.get("return_content", False)
62
+ chunk_size = arguments.get("chunk_size", 8192)
63
+ follow_redirects = arguments.get("follow_redirects", True)
64
+
65
+ # Determine output path
66
+ if output_path:
67
+ output_path = self._normalize_path(output_path)
68
+ output_dir = os.path.dirname(output_path)
69
+ if output_dir and not os.path.exists(output_dir):
70
+ try:
71
+ os.makedirs(output_dir, exist_ok=True)
72
+ except Exception as e:
73
+ return {"error": f"Failed to create directory: {e}"}
74
+ else:
75
+ temp_dir = tempfile.gettempdir()
76
+ parsed_url = urlparse(url)
77
+ filename = os.path.basename(parsed_url.path) or "downloaded_file"
78
+ filename = filename.split("?")[0]
79
+ if not filename:
80
+ filename = "downloaded_file"
81
+ output_path = os.path.join(temp_dir, filename)
82
+ try:
83
+ response = requests.get(
84
+ url, timeout=timeout, allow_redirects=follow_redirects, stream=True
85
+ )
86
+ response.raise_for_status()
87
+
88
+ if return_content:
89
+ content = response.text
90
+ content_type = response.headers.get("Content-Type", "").lower()
91
+ return {
92
+ "content": content,
93
+ "content_type": content_type,
94
+ "size": len(content),
95
+ "url": url,
96
+ }
97
+
98
+ file_size = 0
99
+ with open(output_path, "wb") as f:
100
+ for chunk in response.iter_content(chunk_size=chunk_size):
101
+ if chunk:
102
+ f.write(chunk)
103
+ file_size += len(chunk)
104
+
105
+ return {
106
+ "file_path": output_path,
107
+ "file_size": file_size,
108
+ "url": url,
109
+ "content_type": response.headers.get("Content-Type", ""),
110
+ "status_code": response.status_code,
111
+ }
112
+
113
+ except requests.exceptions.Timeout:
114
+ return {"error": f"Request timed out after {timeout} seconds"}
115
+ except requests.exceptions.HTTPError as e:
116
+ return {"error": f"HTTP error: {e}"}
117
+ except requests.exceptions.ConnectionError as e:
118
+ return {"error": f"Connection error: {e}"}
119
+ except requests.exceptions.RequestException as e:
120
+ return {"error": f"Request failed: {e}"}
121
+ except IOError as e:
122
+ return {"error": f"Failed to write file: {e}"}
123
+ except Exception as e:
124
+ return {"error": f"Unexpected error: {e}"}
125
+
126
+ def _normalize_path(self, path: str) -> str:
127
+ """
128
+ Normalize file path for cross-platform compatibility.
129
+
130
+ Args:
131
+ path: File path to normalize
132
+
133
+ Returns:
134
+ Normalized path
135
+ """
136
+ path = os.path.expanduser(path)
137
+ path = os.path.expandvars(path)
138
+
139
+ if not os.path.isabs(path):
140
+ path = os.path.abspath(path)
141
+
142
+ return path
143
+
144
+
145
+ @register_tool("BinaryDownloadTool")
146
+ class BinaryDownloadTool(BaseTool):
147
+ """
148
+ Download binary files with chunked streaming.
149
+
150
+ Optimized for large binary files like images, videos, executables.
151
+ Supports chunked downloads for better memory management.
152
+
153
+ Expects: {"url": "https://...", "output_path": "/path/to/save"}
154
+ Optional: {"chunk_size": bytes, "timeout": seconds}
155
+ Returns: {"file_path": "...", "size": bytes, "content_type": "..."}
156
+ """
157
+
158
+ def __init__(self, tool_config):
159
+ super().__init__(tool_config)
160
+
161
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
162
+ """
163
+ Download binary file from URL.
164
+
165
+ Args:
166
+ arguments: Dictionary containing url and optional parameters
167
+
168
+ Returns:
169
+ Dictionary with file_path and metadata, or error
170
+ """
171
+ url = arguments.get("url")
172
+ if not url:
173
+ return {"error": "Parameter 'url' is required."}
174
+
175
+ output_path = arguments.get("output_path")
176
+ if not output_path:
177
+ msg = "Parameter 'output_path' is required for binary downloads."
178
+ return {"error": msg}
179
+
180
+ timeout = arguments.get("timeout", 30)
181
+ # 1MB chunks for binary files
182
+ chunk_size = arguments.get("chunk_size", 1024 * 1024)
183
+
184
+ output_path = os.path.expanduser(output_path)
185
+ output_path = os.path.expandvars(output_path)
186
+
187
+ output_dir = os.path.dirname(output_path)
188
+ if output_dir and not os.path.exists(output_dir):
189
+ try:
190
+ os.makedirs(output_dir, exist_ok=True)
191
+ except Exception as e:
192
+ return {"error": f"Failed to create directory: {e}"}
193
+
194
+ try:
195
+ response = requests.get(url, timeout=timeout, stream=True)
196
+ response.raise_for_status()
197
+
198
+ file_size = 0
199
+ with open(output_path, "wb") as f:
200
+ for chunk in response.iter_content(chunk_size=chunk_size):
201
+ if chunk:
202
+ f.write(chunk)
203
+ file_size += len(chunk)
204
+
205
+ return {
206
+ "file_path": output_path,
207
+ "size": file_size,
208
+ "content_type": response.headers.get("Content-Type", ""),
209
+ "content_length": response.headers.get("Content-Length", ""),
210
+ "url": url,
211
+ "status_code": response.status_code,
212
+ }
213
+
214
+ except Exception as e:
215
+ return {"error": f"Failed to download binary file: {e}"}
216
+
217
+
218
+ @register_tool("TextDownloadTool")
219
+ class TextDownloadTool(BaseTool):
220
+ """
221
+ Download and return text content from URLs.
222
+
223
+ Optimized for text files - returns content as string directly.
224
+ Supports encoding detection and normalization.
225
+
226
+ Expects: {"url": "https://..."}
227
+ Optional: {"encoding": "utf-8", "timeout": seconds}
228
+ Returns: {"content": "text content", "encoding": "utf-8"}
229
+ """
230
+
231
+ def __init__(self, tool_config):
232
+ super().__init__(tool_config)
233
+
234
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
235
+ """
236
+ Download text content from URL.
237
+
238
+ Args:
239
+ arguments: Dictionary containing url and optional parameters
240
+
241
+ Returns:
242
+ Dictionary with content and encoding, or error
243
+ """
244
+ url = arguments.get("url")
245
+ if not url:
246
+ return {"error": "Parameter 'url' is required."}
247
+
248
+ timeout = arguments.get("timeout", 30)
249
+ encoding = arguments.get("encoding", None) # Auto-detect if None
250
+
251
+ try:
252
+ response = requests.get(url, timeout=timeout)
253
+ response.raise_for_status()
254
+
255
+ if encoding:
256
+ content = response.content.decode(encoding)
257
+ else:
258
+ content = response.text
259
+
260
+ return {
261
+ "content": content,
262
+ "encoding": response.encoding,
263
+ "size": len(content),
264
+ "url": url,
265
+ "content_type": response.headers.get("Content-Type", ""),
266
+ }
267
+
268
+ except Exception as e:
269
+ return {"error": f"Failed to download text content: {e}"}
tooluniverse/geo_tool.py CHANGED
@@ -7,18 +7,15 @@ next-generation sequencing, and other forms of high-throughput functional
7
7
  genomics data.
8
8
  """
9
9
 
10
- import requests
11
10
  from typing import Dict, Any, List
12
- from .base_tool import BaseTool
11
+ from .ncbi_eutils_tool import NCBIEUtilsTool
13
12
  from .tool_registry import register_tool
14
13
 
15
- GEO_BASE_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
16
-
17
14
 
18
15
  @register_tool("GEORESTTool")
19
- class GEORESTTool(BaseTool):
16
+ class GEORESTTool(NCBIEUtilsTool):
20
17
  """
21
- GEO Database REST API tool.
18
+ GEO Database REST API tool with rate limiting.
22
19
  Generic wrapper for GEO API endpoints defined in expression_tools.json.
23
20
  """
24
21
 
@@ -34,7 +31,7 @@ class GEORESTTool(BaseTool):
34
31
  def _build_url(self, arguments: Dict[str, Any]) -> str | Dict[str, Any]:
35
32
  """Build URL for GEO API request."""
36
33
  url_path = self.endpoint_template
37
- return GEO_BASE_URL + url_path
34
+ return self.base_url + url_path
38
35
 
39
36
  def _build_params(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
40
37
  """Build parameters for GEO API request."""
@@ -85,22 +82,6 @@ class GEORESTTool(BaseTool):
85
82
 
86
83
  return params
87
84
 
88
- def _make_request(self, url: str, params: Dict[str, Any]) -> Dict[str, Any]:
89
- """Perform a GET request and handle common errors."""
90
- try:
91
- response = requests.get(url, params=params, timeout=30)
92
- response.raise_for_status()
93
-
94
- if self.output_format == "JSON":
95
- return response.json()
96
- else:
97
- return {"data": response.text}
98
-
99
- except requests.exceptions.RequestException as e:
100
- return {"error": f"Request failed: {str(e)}"}
101
- except Exception as e:
102
- return {"error": f"Unexpected error: {str(e)}"}
103
-
104
85
  def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
105
86
  """Execute the tool with given arguments."""
106
87
  # Validate required parameters
@@ -108,9 +89,81 @@ class GEORESTTool(BaseTool):
108
89
  if param not in arguments:
109
90
  return {"error": f"Missing required parameter: {param}"}
110
91
 
111
- url = self._build_url(arguments)
112
- if isinstance(url, dict) and "error" in url:
113
- return url
114
-
92
+ # Set endpoint for the base class
93
+ self.endpoint = self.endpoint_template
115
94
  params = self._build_params(arguments)
116
- return self._make_request(url, params)
95
+
96
+ # Use the parent class's _make_request with rate limiting
97
+ return self._make_request(self.endpoint, params)
98
+
99
+
100
+ @register_tool("GEOSearchDatasets")
101
+ class GEOSearchDatasets(GEORESTTool):
102
+ """Search GEO datasets by various criteria."""
103
+
104
+ def __init__(self, tool_config):
105
+ super().__init__(tool_config)
106
+ self.endpoint_template = "/esearch.fcgi"
107
+
108
+ def _build_params(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
109
+ """Build parameters for GEO dataset search."""
110
+ params = {"db": "gds", "retmode": "json", "retmax": 50}
111
+
112
+ # Build search query
113
+ query_parts = []
114
+ if "query" in arguments:
115
+ query_parts.append(arguments["query"])
116
+
117
+ if "organism" in arguments:
118
+ organism = arguments["organism"]
119
+ query_parts.append(f'"{organism}"[organism]')
120
+
121
+ if "study_type" in arguments:
122
+ study_type = arguments["study_type"]
123
+ query_parts.append(f'"{study_type}"[study_type]')
124
+
125
+ if "platform" in arguments:
126
+ platform = arguments["platform"]
127
+ query_parts.append(f'"{platform}"[platform]')
128
+
129
+ if query_parts:
130
+ params["term"] = " AND ".join(query_parts)
131
+
132
+ if "limit" in arguments:
133
+ params["retmax"] = min(arguments["limit"], 500)
134
+
135
+ return params
136
+
137
+
138
+ @register_tool("GEOGetDatasetInfo")
139
+ class GEOGetDatasetInfo(GEORESTTool):
140
+ """Get detailed information about a specific GEO dataset."""
141
+
142
+ def __init__(self, tool_config):
143
+ super().__init__(tool_config)
144
+ self.endpoint_template = "/esummary.fcgi"
145
+
146
+ def _build_params(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
147
+ """Build parameters for GEO dataset info retrieval."""
148
+ dataset_id = arguments.get("dataset_id", "")
149
+ if not dataset_id:
150
+ return {"error": "dataset_id is required"}
151
+
152
+ return {"db": "gds", "id": dataset_id, "retmode": "json"}
153
+
154
+
155
+ @register_tool("GEOGetSampleInfo")
156
+ class GEOGetSampleInfo(GEORESTTool):
157
+ """Get sample information for a GEO dataset."""
158
+
159
+ def __init__(self, tool_config):
160
+ super().__init__(tool_config)
161
+ self.endpoint_template = "/esummary.fcgi"
162
+
163
+ def _build_params(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
164
+ """Build parameters for GEO sample info retrieval."""
165
+ dataset_id = arguments.get("dataset_id", "")
166
+ if not dataset_id:
167
+ return {"error": "dataset_id is required"}
168
+
169
+ return {"db": "gds", "id": dataset_id, "retmode": "json"}
@@ -1,63 +1,111 @@
1
+ """
2
+ gnomAD GraphQL API Tool
3
+
4
+ This tool provides access to the gnomAD (Genome Aggregation Database) for
5
+ population genetics data, variant frequencies, and gene constraint metrics using GraphQL.
6
+ """
7
+
1
8
  import requests
2
- import json
9
+ from typing import Dict, Any
3
10
  from .base_tool import BaseTool
4
11
  from .tool_registry import register_tool
12
+ from .graphql_tool import execute_query
5
13
 
6
14
 
7
- @register_tool("GnomadTool")
8
- class GnomadTool(BaseTool):
9
- """
10
- Local tool wrapper for gnomAD GraphQL API.
11
- Queries variant information including allele frequencies.
12
- """
15
+ class gnomADGraphQLTool(BaseTool):
16
+ """Base class for gnomAD GraphQL API tools."""
13
17
 
14
18
  def __init__(self, tool_config):
15
19
  super().__init__(tool_config)
16
- self.base = "https://gnomad.broadinstitute.org/api"
20
+ self.endpoint_url = "https://gnomad.broadinstitute.org/api"
21
+ self.query_schema = tool_config.get("query_schema", "")
17
22
  self.session = requests.Session()
18
- self.session.headers.update({"Content-Type": "application/json"})
19
-
20
- def run(self, arguments):
21
- variant_id = arguments.get("variant_id")
22
- dataset = arguments.get("dataset", "gnomad_r4")
23
-
24
- if not variant_id:
25
- return {"error": "Missing required parameter: variant_id"}
26
-
27
- # GraphQL query for variant with genome frequencies
28
- query = """
29
- query($variant: String!, $dataset: DatasetId!) {
30
- variant(variantId: $variant, dataset: $dataset) {
31
- variantId
32
- genome {
33
- ac
34
- an
35
- af
23
+ self.session.headers.update(
24
+ {
25
+ "Accept": "application/json",
26
+ "Content-Type": "application/json",
27
+ "User-Agent": "ToolUniverse/1.0",
28
+ }
29
+ )
30
+ self.timeout = 30
31
+
32
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
33
+ """Execute GraphQL query with given arguments."""
34
+ try:
35
+ result = execute_query(
36
+ endpoint_url=self.endpoint_url,
37
+ query=self.query_schema,
38
+ variables=arguments,
39
+ )
40
+
41
+ if result is None:
42
+ return {
43
+ "status": "error",
44
+ "error": "No data returned from gnomAD API",
45
+ "data": None,
36
46
  }
47
+
48
+ return {"status": "success", "data": result, "url": self.endpoint_url}
49
+
50
+ except Exception as e:
51
+ return {
52
+ "status": "error",
53
+ "error": f"gnomAD GraphQL request failed: {str(e)}",
54
+ "data": None,
37
55
  }
38
- }
39
- """
40
-
41
- payload = {
42
- "query": query,
43
- "variables": {
44
- "variant": variant_id,
45
- "dataset": dataset,
46
- },
47
- }
48
-
49
- resp = self.session.post(self.base, data=json.dumps(payload), timeout=30)
50
- resp.raise_for_status()
51
- data = resp.json()
52
-
53
- if "errors" in data:
54
- return {"error": f"GraphQL errors: {data['errors']}"}
55
-
56
- variant = data.get("data", {}).get("variant")
57
- if not variant:
58
- return {"error": "Variant not found"}
59
-
60
- return {
61
- "variantId": variant.get("variantId"),
62
- "genome": variant.get("genome", {}),
63
- }
56
+
57
+
58
+ @register_tool("gnomADGetGeneConstraints")
59
+ class gnomADGetGeneConstraints(gnomADGraphQLTool):
60
+ """Get gene constraint metrics from gnomAD."""
61
+
62
+ def __init__(self, tool_config):
63
+ super().__init__(tool_config)
64
+ # Set default query schema if not provided in config
65
+ if not self.query_schema:
66
+ self.query_schema = """
67
+ query GeneConstraints($geneSymbol: String!) {
68
+ gene(gene_symbol: $geneSymbol, reference_genome: GRCh38) {
69
+ symbol
70
+ gene_id
71
+ exac_constraint {
72
+ exp_lof
73
+ obs_lof
74
+ pLI
75
+ exp_mis
76
+ obs_mis
77
+ exp_syn
78
+ obs_syn
79
+ }
80
+ gnomad_constraint {
81
+ exp_lof
82
+ obs_lof
83
+ oe_lof
84
+ pLI
85
+ exp_mis
86
+ obs_mis
87
+ oe_mis
88
+ exp_syn
89
+ obs_syn
90
+ oe_syn
91
+ }
92
+ }
93
+ }
94
+ """
95
+
96
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
97
+ """Get gene constraints."""
98
+ gene_symbol = arguments.get("gene_symbol", "")
99
+ if not gene_symbol:
100
+ return {"status": "error", "error": "gene_symbol is required"}
101
+
102
+ # Convert gene_symbol to geneSymbol for GraphQL variable
103
+ graphql_args = {"geneSymbol": gene_symbol}
104
+
105
+ result = super().run(graphql_args)
106
+
107
+ # Add gene_symbol to result for reference
108
+ if result.get("status") == "success":
109
+ result["gene_symbol"] = gene_symbol
110
+
111
+ return result
@@ -0,0 +1,41 @@
1
+ import requests
2
+ from typing import Any, Dict
3
+ from .base_tool import BaseTool
4
+ from .tool_registry import register_tool
5
+
6
+
7
+ @register_tool("GtoPdbRESTTool")
8
+ class GtoPdbRESTTool(BaseTool):
9
+ def __init__(self, tool_config: Dict):
10
+ super().__init__(tool_config)
11
+ self.base_url = "https://www.guidetopharmacology.org/services"
12
+ self.session = requests.Session()
13
+ self.session.headers.update({"Accept": "application/json"})
14
+ self.timeout = 30
15
+
16
+ def _build_url(self, args: Dict[str, Any]) -> str:
17
+ url = self.tool_config["fields"]["endpoint"]
18
+ for k, v in args.items():
19
+ url = url.replace(f"{{{k}}}", str(v))
20
+ return url
21
+
22
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
23
+ try:
24
+ url = self._build_url(arguments)
25
+ response = self.session.get(url, timeout=self.timeout)
26
+ response.raise_for_status()
27
+ data = response.json()
28
+
29
+ # Apply limit if specified
30
+ limit = arguments.get("limit", 20)
31
+ if isinstance(data, list) and len(data) > limit:
32
+ data = data[:limit]
33
+
34
+ return {
35
+ "status": "success",
36
+ "data": data,
37
+ "url": url,
38
+ "count": len(data) if isinstance(data, list) else 1,
39
+ }
40
+ except Exception as e:
41
+ return {"status": "error", "error": f"GtoPdb API error: {str(e)}"}
@@ -0,0 +1,72 @@
1
+ import requests
2
+ from typing import Any, Dict
3
+ from .base_tool import BaseTool
4
+ from .tool_registry import register_tool
5
+
6
+
7
+ @register_tool("InterProRESTTool")
8
+ class InterProRESTTool(BaseTool):
9
+ def __init__(self, tool_config: Dict):
10
+ super().__init__(tool_config)
11
+ self.base_url = "https://www.ebi.ac.uk/interpro/api"
12
+ self.session = requests.Session()
13
+ self.session.headers.update(
14
+ {"Accept": "application/json", "User-Agent": "ToolUniverse/1.0"}
15
+ )
16
+ self.timeout = 30
17
+
18
+ def _build_url(self, args: Dict[str, Any]) -> str:
19
+ """Build URL from endpoint template and arguments"""
20
+ url = self.tool_config["fields"]["endpoint"]
21
+ for k, v in args.items():
22
+ url = url.replace(f"{{{k}}}", str(v))
23
+ return url
24
+
25
+ def _extract_data(self, data: Dict, extract_path: str = None) -> Any:
26
+ """Extract specific data from API response"""
27
+ if not extract_path:
28
+ return data
29
+
30
+ # Handle specific InterPro extraction patterns
31
+ if extract_path == "results":
32
+ return data.get("results", [])
33
+ elif extract_path == "count":
34
+ return data.get("count", 0)
35
+ elif extract_path == "metadata":
36
+ return data.get("metadata", {})
37
+
38
+ return data
39
+
40
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
41
+ """Execute the InterPro API call"""
42
+ try:
43
+ # Build URL from endpoint template
44
+ url = self._build_url(arguments)
45
+
46
+ # Make API request
47
+ response = self.session.get(url, timeout=self.timeout)
48
+ response.raise_for_status()
49
+
50
+ # Parse JSON response
51
+ data = response.json()
52
+
53
+ # Extract data if specified
54
+ extract_path = self.tool_config["fields"].get("extract_path")
55
+ if extract_path:
56
+ result = self._extract_data(data, extract_path)
57
+ else:
58
+ result = data
59
+
60
+ return {
61
+ "status": "success",
62
+ "data": result,
63
+ "url": url,
64
+ "count": len(result) if isinstance(result, list) else 1,
65
+ }
66
+
67
+ except Exception as e:
68
+ return {
69
+ "status": "error",
70
+ "error": f"InterPro API error: {str(e)}",
71
+ "url": url,
72
+ }