tooluniverse 0.1.4__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.

Files changed (187) hide show
  1. tooluniverse/__init__.py +340 -4
  2. tooluniverse/admetai_tool.py +84 -0
  3. tooluniverse/agentic_tool.py +563 -0
  4. tooluniverse/alphafold_tool.py +96 -0
  5. tooluniverse/base_tool.py +129 -6
  6. tooluniverse/boltz_tool.py +207 -0
  7. tooluniverse/chem_tool.py +192 -0
  8. tooluniverse/compose_scripts/__init__.py +1 -0
  9. tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
  10. tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
  11. tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
  12. tooluniverse/compose_scripts/literature_tool.py +34 -0
  13. tooluniverse/compose_scripts/output_summarizer.py +279 -0
  14. tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
  15. tooluniverse/compose_scripts/tool_discover.py +705 -0
  16. tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
  17. tooluniverse/compose_tool.py +371 -0
  18. tooluniverse/ctg_tool.py +1002 -0
  19. tooluniverse/custom_tool.py +81 -0
  20. tooluniverse/dailymed_tool.py +108 -0
  21. tooluniverse/data/admetai_tools.json +155 -0
  22. tooluniverse/data/agentic_tools.json +1156 -0
  23. tooluniverse/data/alphafold_tools.json +87 -0
  24. tooluniverse/data/boltz_tools.json +9 -0
  25. tooluniverse/data/chembl_tools.json +16 -0
  26. tooluniverse/data/clait_tools.json +108 -0
  27. tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
  28. tooluniverse/data/compose_tools.json +202 -0
  29. tooluniverse/data/dailymed_tools.json +70 -0
  30. tooluniverse/data/dataset_tools.json +646 -0
  31. tooluniverse/data/disease_target_score_tools.json +712 -0
  32. tooluniverse/data/efo_tools.json +17 -0
  33. tooluniverse/data/embedding_tools.json +319 -0
  34. tooluniverse/data/enrichr_tools.json +31 -0
  35. tooluniverse/data/europe_pmc_tools.json +22 -0
  36. tooluniverse/data/expert_feedback_tools.json +10 -0
  37. tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
  38. tooluniverse/data/fda_drug_labeling_tools.json +544 -168
  39. tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
  40. tooluniverse/data/finder_tools.json +209 -0
  41. tooluniverse/data/gene_ontology_tools.json +113 -0
  42. tooluniverse/data/gwas_tools.json +1082 -0
  43. tooluniverse/data/hpa_tools.json +333 -0
  44. tooluniverse/data/humanbase_tools.json +47 -0
  45. tooluniverse/data/idmap_tools.json +74 -0
  46. tooluniverse/data/mcp_client_tools_example.json +113 -0
  47. tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
  48. tooluniverse/data/medlineplus_tools.json +141 -0
  49. tooluniverse/data/monarch_tools.json +1 -1
  50. tooluniverse/data/openalex_tools.json +36 -0
  51. tooluniverse/data/opentarget_tools.json +82 -58
  52. tooluniverse/data/output_summarization_tools.json +101 -0
  53. tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
  54. tooluniverse/data/packages/categorized_tools.txt +206 -0
  55. tooluniverse/data/packages/cheminformatics_tools.json +347 -0
  56. tooluniverse/data/packages/earth_sciences_tools.json +74 -0
  57. tooluniverse/data/packages/genomics_tools.json +776 -0
  58. tooluniverse/data/packages/image_processing_tools.json +38 -0
  59. tooluniverse/data/packages/machine_learning_tools.json +789 -0
  60. tooluniverse/data/packages/neuroscience_tools.json +62 -0
  61. tooluniverse/data/packages/original_tools.txt +0 -0
  62. tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
  63. tooluniverse/data/packages/scientific_computing_tools.json +560 -0
  64. tooluniverse/data/packages/single_cell_tools.json +453 -0
  65. tooluniverse/data/packages/software_tools.json +4954 -0
  66. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  67. tooluniverse/data/packages/visualization_tools.json +399 -0
  68. tooluniverse/data/pubchem_tools.json +215 -0
  69. tooluniverse/data/pubtator_tools.json +68 -0
  70. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  71. tooluniverse/data/reactome_tools.json +19 -0
  72. tooluniverse/data/semantic_scholar_tools.json +26 -0
  73. tooluniverse/data/special_tools.json +2 -25
  74. tooluniverse/data/tool_composition_tools.json +88 -0
  75. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  76. tooluniverse/data/txagent_client_tools.json +9 -0
  77. tooluniverse/data/uniprot_tools.json +211 -0
  78. tooluniverse/data/url_fetch_tools.json +94 -0
  79. tooluniverse/data/uspto_downloader_tools.json +9 -0
  80. tooluniverse/data/uspto_tools.json +811 -0
  81. tooluniverse/data/xml_tools.json +3275 -0
  82. tooluniverse/dataset_tool.py +296 -0
  83. tooluniverse/default_config.py +165 -0
  84. tooluniverse/efo_tool.py +42 -0
  85. tooluniverse/embedding_database.py +630 -0
  86. tooluniverse/embedding_sync.py +396 -0
  87. tooluniverse/enrichr_tool.py +266 -0
  88. tooluniverse/europe_pmc_tool.py +52 -0
  89. tooluniverse/execute_function.py +1775 -95
  90. tooluniverse/extended_hooks.py +444 -0
  91. tooluniverse/gene_ontology_tool.py +194 -0
  92. tooluniverse/graphql_tool.py +158 -36
  93. tooluniverse/gwas_tool.py +358 -0
  94. tooluniverse/hpa_tool.py +1645 -0
  95. tooluniverse/humanbase_tool.py +389 -0
  96. tooluniverse/logging_config.py +254 -0
  97. tooluniverse/mcp_client_tool.py +764 -0
  98. tooluniverse/mcp_integration.py +413 -0
  99. tooluniverse/mcp_tool_registry.py +925 -0
  100. tooluniverse/medlineplus_tool.py +337 -0
  101. tooluniverse/openalex_tool.py +228 -0
  102. tooluniverse/openfda_adv_tool.py +283 -0
  103. tooluniverse/openfda_tool.py +393 -160
  104. tooluniverse/output_hook.py +1122 -0
  105. tooluniverse/package_tool.py +195 -0
  106. tooluniverse/pubchem_tool.py +158 -0
  107. tooluniverse/pubtator_tool.py +168 -0
  108. tooluniverse/rcsb_pdb_tool.py +38 -0
  109. tooluniverse/reactome_tool.py +108 -0
  110. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  111. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  112. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  113. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  114. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  115. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  116. tooluniverse/remote/expert_feedback_mcp/human_expert_mcp_server.py +1611 -0
  117. tooluniverse/remote/expert_feedback_mcp/simple_test.py +34 -0
  118. tooluniverse/remote/expert_feedback_mcp/start_web_interface.py +91 -0
  119. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  120. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  121. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  122. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  123. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  124. tooluniverse/remote_tool.py +99 -0
  125. tooluniverse/restful_tool.py +53 -30
  126. tooluniverse/scripts/generate_tool_graph.py +408 -0
  127. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  128. tooluniverse/semantic_scholar_tool.py +62 -0
  129. tooluniverse/smcp.py +2452 -0
  130. tooluniverse/smcp_server.py +975 -0
  131. tooluniverse/test/mcp_server_test.py +0 -0
  132. tooluniverse/test/test_admetai_tool.py +370 -0
  133. tooluniverse/test/test_agentic_tool.py +129 -0
  134. tooluniverse/test/test_alphafold_tool.py +71 -0
  135. tooluniverse/test/test_chem_tool.py +37 -0
  136. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  137. tooluniverse/test/test_compose_tool.py +448 -0
  138. tooluniverse/test/test_dailymed.py +69 -0
  139. tooluniverse/test/test_dataset_tool.py +200 -0
  140. tooluniverse/test/test_disease_target_score.py +56 -0
  141. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  142. tooluniverse/test/test_efo.py +31 -0
  143. tooluniverse/test/test_enrichr_tool.py +21 -0
  144. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  145. tooluniverse/test/test_fda_adv.py +95 -0
  146. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  147. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  148. tooluniverse/test/test_gwas_tool.py +139 -0
  149. tooluniverse/test/test_hpa.py +625 -0
  150. tooluniverse/test/test_humanbase_tool.py +20 -0
  151. tooluniverse/test/test_idmap_tools.py +61 -0
  152. tooluniverse/test/test_mcp_server.py +211 -0
  153. tooluniverse/test/test_mcp_tool.py +247 -0
  154. tooluniverse/test/test_medlineplus.py +220 -0
  155. tooluniverse/test/test_openalex_tool.py +32 -0
  156. tooluniverse/test/test_opentargets.py +28 -0
  157. tooluniverse/test/test_pubchem_tool.py +116 -0
  158. tooluniverse/test/test_pubtator_tool.py +37 -0
  159. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  160. tooluniverse/test/test_reactome.py +54 -0
  161. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  162. tooluniverse/test/test_software_tools.py +147 -0
  163. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  164. tooluniverse/test/test_tool_finder.py +26 -0
  165. tooluniverse/test/test_tool_finder_llm.py +252 -0
  166. tooluniverse/test/test_tools_find.py +195 -0
  167. tooluniverse/test/test_uniprot_tools.py +74 -0
  168. tooluniverse/test/test_uspto_tool.py +72 -0
  169. tooluniverse/test/test_xml_tool.py +113 -0
  170. tooluniverse/tool_finder_embedding.py +267 -0
  171. tooluniverse/tool_finder_keyword.py +693 -0
  172. tooluniverse/tool_finder_llm.py +699 -0
  173. tooluniverse/tool_graph_web_ui.py +955 -0
  174. tooluniverse/tool_registry.py +416 -0
  175. tooluniverse/uniprot_tool.py +155 -0
  176. tooluniverse/url_tool.py +253 -0
  177. tooluniverse/uspto_tool.py +240 -0
  178. tooluniverse/utils.py +369 -41
  179. tooluniverse/xml_tool.py +369 -0
  180. tooluniverse-1.0.0.dist-info/METADATA +377 -0
  181. tooluniverse-1.0.0.dist-info/RECORD +186 -0
  182. {tooluniverse-0.1.4.dist-info → tooluniverse-1.0.0.dist-info}/WHEEL +1 -1
  183. tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
  184. tooluniverse-0.1.4.dist-info/METADATA +0 -141
  185. tooluniverse-0.1.4.dist-info/RECORD +0 -18
  186. {tooluniverse-0.1.4.dist-info → tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
  187. {tooluniverse-0.1.4.dist-info → tooluniverse-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,389 @@
1
+ import networkx as nx
2
+ import requests
3
+ import urllib.parse
4
+ from .base_tool import BaseTool
5
+ from .tool_registry import register_tool
6
+
7
+
8
+ @register_tool("HumanBaseTool")
9
+ class HumanBaseTool(BaseTool):
10
+ """
11
+ Tool to retrieve protein-protein interactions and biological processes from HumanBase.
12
+ """
13
+
14
+ def __init__(self, tool_config):
15
+ super().__init__(tool_config)
16
+
17
+ def run(self, arguments):
18
+ """Main entry point for the tool."""
19
+ gene_list = arguments.get("gene_list")
20
+ tissue = arguments.get("tissue", "brain")
21
+ max_node = arguments.get("max_node", 10)
22
+ interaction = arguments.get("interaction", None)
23
+ string_mode = arguments.get("string_mode", True)
24
+
25
+ graph, bp_collection = self.humanbase_ppi_retrieve(
26
+ gene_list, tissue, max_node, interaction
27
+ )
28
+
29
+ if string_mode:
30
+ return self._convert_to_string(graph, bp_collection, gene_list, tissue)
31
+ else:
32
+ return graph, bp_collection
33
+
34
+ def get_official_gene_name(self, gene_name):
35
+ """
36
+ Retrieve the official gene symbol (same as EnrichrTool method)
37
+
38
+ Parameters:
39
+ gene_name (str): The gene name or synonym to query.
40
+
41
+ Returns:
42
+ str: The official gene symbol.
43
+ """
44
+ """
45
+ Retrieve the official gene symbol for a given gene name or synonym using the MyGene.info API.
46
+
47
+ Parameters:
48
+ gene_name (str): The gene name or synonym to query.
49
+
50
+ Returns:
51
+ str: The official gene symbol if found; otherwise, raises an Exception.
52
+ """
53
+ # URL-encode the gene_name to handle special characters
54
+ encoded_gene_name = urllib.parse.quote(gene_name)
55
+ url = f"https://mygene.info/v3/query?q={encoded_gene_name}&fields=symbol,alias&species=human"
56
+
57
+ response = requests.get(url)
58
+ if response.status_code != 200:
59
+ return f"Error querying MyGene.info API: {response.status_code}"
60
+
61
+ data = response.json()
62
+ hits = data.get("hits", [])
63
+ if not hits:
64
+ return f"No data found for: {gene_name}. Please check the gene name and try again."
65
+
66
+ # Attempt to find an exact match in the official symbol or among aliases.
67
+ for hit in hits:
68
+ symbol = hit.get("symbol", "")
69
+ if symbol.upper() == gene_name.upper():
70
+ print(
71
+ f"[humanbase_tool] Using the official gene name: '{symbol}' instead of {gene_name}",
72
+ flush=True,
73
+ )
74
+ return symbol
75
+ aliases = hit.get("alias", [])
76
+ if any(gene_name.upper() == alias.upper() for alias in aliases):
77
+ print(
78
+ f"[humanbase_tool] Using the official gene name: '{symbol}' instead of {gene_name}",
79
+ flush=True,
80
+ )
81
+ return symbol
82
+
83
+ # If no exact match is found, return the symbol of the top hit.
84
+ top_hit = hits[0]
85
+ symbol = top_hit.get("symbol", None)
86
+ if symbol:
87
+ print(
88
+ f"[humanbase_tool] Using the official gene name: '{symbol}' instead of {gene_name}",
89
+ flush=True,
90
+ )
91
+ return symbol
92
+ else:
93
+ return f"No official gene symbol found for: {gene_name}. Please ensure it is correct."
94
+
95
+ def get_entrez_ids(self, gene_names):
96
+ """
97
+ Convert gene names to Entrez IDs using NCBI Entrez API.
98
+
99
+ Parameters:
100
+ gene_names (list): List of gene names to convert.
101
+
102
+ Returns:
103
+ list: List of Entrez IDs corresponding to the gene names.
104
+ """
105
+ # Define the NCBI Entrez API URL for querying gene information
106
+ url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
107
+
108
+ # Initialize a list to store Entrez IDs
109
+ entrez_ids = []
110
+ gene_names = [self.get_official_gene_name(gene) for gene in gene_names]
111
+
112
+ # Loop over each gene name in the input list
113
+ for gene in gene_names:
114
+ # Define the parameters for the API request
115
+ params = {
116
+ "db": "gene", # Specify the database to search in (gene)
117
+ "term": gene
118
+ + "[gene] AND Homo sapiens[orgn]", # Query term with organism filter
119
+ "retmode": "xml", # Request the output in XML format
120
+ "retmax": "1", # We only want the first result
121
+ }
122
+
123
+ # Send the request to the Entrez API
124
+ response = requests.get(url, params=params)
125
+
126
+ # Check if the response was successful
127
+ if response.status_code == 200:
128
+ # Parse the XML response
129
+ xml_data = response.text
130
+
131
+ # Find the Entrez Gene ID in the XML response
132
+ start_idx = xml_data.find("<Id>")
133
+ end_idx = xml_data.find("</Id>")
134
+
135
+ if start_idx != -1 and end_idx != -1:
136
+ # Extract and append the Entrez Gene ID to the list
137
+ entrez_id = xml_data[start_idx + 4 : end_idx]
138
+ entrez_ids.append(entrez_id)
139
+ else:
140
+ # If no Entrez ID is found, append None
141
+ entrez_ids.append(None)
142
+ else:
143
+ # Handle any errors in the API request
144
+ return f"Error fetching data for gene: {gene}. Please check whether the gene uses official gene name."
145
+
146
+ return entrez_ids
147
+
148
+ def humanbase_ppi_retrieve(self, genes, tissue, max_node=10, interaction=None):
149
+ """
150
+ Retrieve protein-protein interactions and biological processes from HumanBase.
151
+
152
+ Parameters:
153
+ genes (list): List of gene names to analyze.
154
+ tissue (str): Tissue type for tissue-specific interactions.
155
+ max_node (int): Maximum number of nodes to retrieve.
156
+ interaction (str): Specific interaction type to filter by.
157
+
158
+ Returns:
159
+ tuple: (NetworkX Graph of interactions, list of biological processes)
160
+ """
161
+ genes = self.get_entrez_ids(genes)
162
+
163
+ tissue = tissue.replace(" ", "-").replace("_", "-").lower()
164
+ interaction_types = [
165
+ "co-expression",
166
+ "interaction",
167
+ "tf-binding",
168
+ "gsea-microrna-targets",
169
+ "gsea-perturbations",
170
+ ]
171
+
172
+ if not interaction or interaction not in interaction_types:
173
+ interaction = "&datatypes=".join(interaction_types)
174
+
175
+ gene_id = "&entrez=".join(genes)
176
+ G = nx.Graph()
177
+ bp_collection = None
178
+
179
+ network_url = f"https://hb.flatironinstitute.org/api/integrations/{tissue}/network/?datatypes={interaction}&entrez={gene_id}&node_size={max_node}"
180
+ edge_type_url = "https://hb.flatironinstitute.org/api/integrations/{tissue}/evidence/?limit=20&source={source}&target={target}"
181
+
182
+ # Retrieve tissue-specific PPI
183
+ try:
184
+ response = requests.get(network_url)
185
+ response.raise_for_status()
186
+ data = response.json()
187
+
188
+ if "genes" in data.keys():
189
+ G.add_nodes_from(
190
+ [
191
+ (
192
+ g["standard_name"],
193
+ {"entrez": g["entrez"], "description": g["description"]},
194
+ )
195
+ for g in data["genes"]
196
+ ]
197
+ )
198
+
199
+ if "edges" in data.keys():
200
+ for e in data["edges"]:
201
+ source = data["genes"][e["source"]]["standard_name"]
202
+ target = data["genes"][e["target"]]["standard_name"]
203
+ weight = e["weight"]
204
+
205
+ edge_response = requests.get(
206
+ edge_type_url.format(
207
+ tissue=tissue,
208
+ source=G.nodes[source]["entrez"],
209
+ target=G.nodes[target]["entrez"],
210
+ )
211
+ )
212
+ edge_response.raise_for_status()
213
+ edge_data = edge_response.json()
214
+ edge_info = {
215
+ t["title"]: t["weight"] for t in edge_data["datatypes"]
216
+ }
217
+
218
+ G.add_edge(source, target, weight=weight, interaction=edge_info)
219
+
220
+ except requests.exceptions.RequestException as exc:
221
+ print(f"Error retrieving PPI data: {exc}")
222
+
223
+ # Check gene ontology (biological process) graph involved
224
+ bp_url = f"https://hb.flatironinstitute.org/api/terms/annotated/?database=gene-ontology-bp&entrez={gene_id}&max_term_size=20"
225
+
226
+ try:
227
+ response = requests.get(bp_url)
228
+ response.raise_for_status()
229
+ data = response.json()
230
+
231
+ if len(data) > 0:
232
+ # Grab the top 20 common pathways
233
+ bp_collection = [bp_entity["title"] for bp_entity in data]
234
+ else:
235
+ print(f"[{genes}] No Gene Ontology Process recorded.")
236
+
237
+ except requests.exceptions.RequestException as exc:
238
+ print(f"Error retrieving biological process data: {exc}")
239
+
240
+ return G, bp_collection
241
+
242
+ def _convert_to_string(self, graph, bp_collection, original_genes, tissue):
243
+ """
244
+ Convert NetworkX graph and biological processes to string representation.
245
+
246
+ Parameters:
247
+ graph (networkx.Graph): The network graph.
248
+ bp_collection (list): List of biological processes.
249
+ original_genes (list): Original gene list provided by user.
250
+ tissue (str): Tissue type used for analysis.
251
+
252
+ Returns:
253
+ str: Comprehensive string representation of the network data.
254
+ """
255
+ output = []
256
+
257
+ # Header information
258
+ output.append("🧬 HUMANBASE PROTEIN-PROTEIN INTERACTION NETWORK")
259
+ output.append("=" * 50)
260
+ output.append(f"Query Genes: {', '.join(original_genes)}")
261
+ output.append(f"Tissue: {tissue.capitalize()}")
262
+ output.append(f"Analysis Date: {self._get_current_timestamp()}")
263
+ output.append("")
264
+
265
+ # Network summary
266
+ num_nodes = graph.number_of_nodes()
267
+ num_edges = graph.number_of_edges()
268
+
269
+ output.append("📊 NETWORK SUMMARY")
270
+ output.append("-" * 20)
271
+ output.append(f"Total Proteins: {num_nodes}")
272
+ output.append(f"Total Interactions: {num_edges}")
273
+
274
+ if num_nodes > 0:
275
+ density = nx.density(graph)
276
+ output.append(f"Network Density: {density:.3f}")
277
+
278
+ output.append("")
279
+
280
+ # Node information
281
+ if num_nodes > 0:
282
+ output.append("🔗 PROTEIN NODES")
283
+ output.append("-" * 15)
284
+ for i, (node, data) in enumerate(graph.nodes(data=True), 1):
285
+ entrez_id = data.get("entrez", "N/A")
286
+ description = data.get("description", "No description available")
287
+ degree = graph.degree(node)
288
+ output.append(f"{i:2d}. {node} (Entrez: {entrez_id})")
289
+ output.append(f" Description: {description}")
290
+ output.append(f" Connections: {degree}")
291
+ output.append("")
292
+
293
+ # Edge information
294
+ if num_edges > 0:
295
+ output.append("⚡ PROTEIN INTERACTIONS")
296
+ output.append("-" * 22)
297
+ for i, (source, target, data) in enumerate(graph.edges(data=True), 1):
298
+ weight = data.get("weight", "N/A")
299
+ interaction_info = data.get("interaction", {})
300
+
301
+ output.append(f"{i:2d}. {source} ↔ {target}")
302
+ output.append(f" Weight: {weight}")
303
+
304
+ if interaction_info:
305
+ output.append(" Evidence Types:")
306
+ for evidence_type, evidence_weight in interaction_info.items():
307
+ output.append(f" • {evidence_type}: {evidence_weight}")
308
+ else:
309
+ output.append(
310
+ " Evidence Types: No detailed information available"
311
+ )
312
+ output.append("")
313
+
314
+ # Biological processes
315
+ if bp_collection:
316
+ output.append("🧬 ASSOCIATED BIOLOGICAL PROCESSES")
317
+ output.append("-" * 35)
318
+ output.append(f"Total Processes: {len(bp_collection)}")
319
+ output.append("")
320
+
321
+ for i, process in enumerate(bp_collection, 1):
322
+ output.append(f"{i:2d}. {process}")
323
+ output.append("")
324
+ else:
325
+ output.append("🧬 ASSOCIATED BIOLOGICAL PROCESSES")
326
+ output.append("-" * 35)
327
+ output.append("No biological processes found for this gene set.")
328
+ output.append("")
329
+
330
+ # Network analysis summary
331
+ if num_nodes > 1:
332
+ output.append("📈 NETWORK ANALYSIS")
333
+ output.append("-" * 18)
334
+
335
+ # Most connected proteins
336
+ if num_nodes > 0:
337
+ degrees = [(node, graph.degree(node)) for node in graph.nodes()]
338
+ degrees.sort(key=lambda x: x[1], reverse=True)
339
+
340
+ output.append("Most Connected Proteins:")
341
+ for i, (node, degree) in enumerate(degrees[:5], 1):
342
+ output.append(f" {i}. {node}: {degree} connections")
343
+ output.append("")
344
+
345
+ # Connectivity
346
+ is_connected = nx.is_connected(graph)
347
+ output.append(
348
+ f"Network Connectivity: {'Fully connected' if is_connected else 'Disconnected components'}"
349
+ )
350
+
351
+ if is_connected and num_nodes > 1:
352
+ try:
353
+ diameter = nx.diameter(graph)
354
+ avg_path_length = nx.average_shortest_path_length(graph)
355
+ output.append(f"Network Diameter: {diameter}")
356
+ output.append(f"Average Path Length: {avg_path_length:.2f}")
357
+ except Exception:
358
+ pass
359
+
360
+ # Clustering
361
+ try:
362
+ clustering = nx.average_clustering(graph)
363
+ output.append(f"Average Clustering: {clustering:.3f}")
364
+ except Exception:
365
+ pass
366
+
367
+ output.append("")
368
+
369
+ # Footer
370
+ output.append("📝 NOTES")
371
+ output.append("-" * 8)
372
+ output.append(
373
+ "• Interaction weights represent confidence scores from HumanBase"
374
+ )
375
+ output.append("• Evidence types indicate the source of interaction data")
376
+ output.append(
377
+ "• Biological processes are derived from Gene Ontology annotations"
378
+ )
379
+ output.append(
380
+ "• Network analysis metrics help understand protein relationship patterns"
381
+ )
382
+
383
+ return "\n".join(output)
384
+
385
+ def _get_current_timestamp(self):
386
+ """Get current timestamp for the report."""
387
+ from datetime import datetime
388
+
389
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -0,0 +1,254 @@
1
+ """
2
+ Global logging configuration for ToolUniverse
3
+
4
+ This module provides a centralized logging system based on Python's standard logging module.
5
+ It allows controlling debug output across the entire ToolUniverse project with different
6
+ verbosity levels.
7
+
8
+ Usage:
9
+ # Set log level via environment variable
10
+ export TOOLUNIVERSE_LOG_LEVEL=DEBUG
11
+
12
+ # Or set programmatically
13
+ from tooluniverse.logging_config import setup_logging, get_logger
14
+ setup_logging('DEBUG')
15
+
16
+ # Use in your code
17
+ logger = get_logger(__name__)
18
+ logger.info("This is an info message")
19
+ logger.debug("This is a debug message")
20
+ """
21
+
22
+ import logging
23
+ import os
24
+ import sys
25
+ from typing import Optional, Callable
26
+
27
+ # Define custom log levels
28
+ PROGRESS_LEVEL = 25 # Between INFO(20) and WARNING(30)
29
+ logging.addLevelName(PROGRESS_LEVEL, "PROGRESS")
30
+
31
+
32
+ class ToolUniverseFormatter(logging.Formatter):
33
+ """Custom formatter with colored output and emoji prefixes"""
34
+
35
+ # Color codes for different log levels
36
+ COLORS = {
37
+ "DEBUG": "\033[36m", # Cyan
38
+ "INFO": "\033[32m", # Green
39
+ "PROGRESS": "\033[34m", # Blue
40
+ "WARNING": "\033[33m", # Yellow
41
+ "ERROR": "\033[31m", # Red
42
+ "CRITICAL": "\033[35m", # Magenta
43
+ "RESET": "\033[0m", # Reset
44
+ }
45
+
46
+ # Emoji prefixes for different log levels
47
+ EMOJI_PREFIX = {
48
+ "DEBUG": "🔧 ",
49
+ "INFO": "ℹ️ ",
50
+ "PROGRESS": "⏳ ",
51
+ "WARNING": "⚠️ ",
52
+ "ERROR": "❌ ",
53
+ "CRITICAL": "🚨 ",
54
+ }
55
+
56
+ def format(self, record):
57
+ # Add emoji prefix
58
+ emoji = self.EMOJI_PREFIX.get(record.levelname, "")
59
+
60
+ # Add color if output is to terminal
61
+ if hasattr(sys.stderr, "isatty") and sys.stderr.isatty():
62
+ color = self.COLORS.get(record.levelname, "")
63
+ reset = self.COLORS["RESET"]
64
+ record.levelname = f"{color}{record.levelname}{reset}"
65
+
66
+ # Format the message
67
+ formatted = super().format(record)
68
+ return f"{emoji}{formatted}"
69
+
70
+
71
+ class ToolUniverseLogger:
72
+ """
73
+ Singleton logger manager for ToolUniverse
74
+ """
75
+
76
+ _instance = None
77
+ _logger = None
78
+ _initialized = False
79
+
80
+ def __new__(cls):
81
+ if cls._instance is None:
82
+ cls._instance = super().__new__(cls)
83
+ return cls._instance
84
+
85
+ def __init__(self):
86
+ if not self._initialized:
87
+ self._setup_logger()
88
+ self._initialized = True
89
+
90
+ def _setup_logger(self):
91
+ """Setup the main ToolUniverse logger"""
92
+ self._logger = logging.getLogger("tooluniverse")
93
+
94
+ # Remove existing handlers to avoid duplicates
95
+ for handler in self._logger.handlers[:]:
96
+ self._logger.removeHandler(handler)
97
+
98
+ # Set initial level from environment or default to INFO
99
+ initial_level = os.getenv("TOOLUNIVERSE_LOG_LEVEL", "INFO").upper()
100
+ try:
101
+ level = getattr(logging, initial_level)
102
+ except AttributeError:
103
+ level = logging.INFO
104
+
105
+ self._logger.setLevel(level)
106
+
107
+ # Create console handler
108
+ handler = logging.StreamHandler(sys.stdout)
109
+ handler.setLevel(level)
110
+
111
+ # Create formatter
112
+ formatter = ToolUniverseFormatter(
113
+ fmt="%(message)s", # Simple format since we add emoji prefix
114
+ datefmt="%H:%M:%S",
115
+ )
116
+ handler.setFormatter(formatter)
117
+
118
+ # Add handler to logger
119
+ self._logger.addHandler(handler)
120
+
121
+ # Prevent propagation to root logger
122
+ self._logger.propagate = False
123
+
124
+ def get_logger(self, name: Optional[str] = None) -> logging.Logger:
125
+ """Get a logger instance"""
126
+ if name:
127
+ return logging.getLogger(f"tooluniverse.{name}")
128
+ # Fallback to module logger if not initialized
129
+ return (
130
+ self._logger
131
+ if self._logger is not None
132
+ else logging.getLogger("tooluniverse")
133
+ )
134
+
135
+ def set_level(self, level: str) -> None:
136
+ """Set logging level"""
137
+ try:
138
+ log_level = getattr(logging, level.upper())
139
+ if self._logger is None:
140
+ return
141
+ self._logger.setLevel(log_level)
142
+ for handler in self._logger.handlers:
143
+ handler.setLevel(log_level)
144
+ except AttributeError:
145
+ if self._logger is not None:
146
+ self._logger.warning(
147
+ f"Invalid log level: {level}. Valid levels: DEBUG, INFO, WARNING, ERROR, CRITICAL"
148
+ )
149
+
150
+
151
+ # Global logger instance
152
+ _logger_manager = ToolUniverseLogger()
153
+
154
+
155
+ def setup_logging(level: Optional[str] = None) -> None:
156
+ """
157
+ Setup global logging configuration
158
+
159
+ Args:
160
+ level (str): Log level ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
161
+ """
162
+ if level:
163
+ _logger_manager.set_level(level)
164
+
165
+
166
+ def get_logger(name: Optional[str] = None) -> logging.Logger:
167
+ """
168
+ Get a logger instance
169
+
170
+ Args:
171
+ name (str, optional): Logger name (usually __name__)
172
+
173
+ Returns:
174
+ logging.Logger: Logger instance
175
+ """
176
+ return _logger_manager.get_logger(name)
177
+
178
+
179
+ def set_log_level(level: str) -> None:
180
+ """Set global log level"""
181
+ _logger_manager.set_level(level)
182
+
183
+
184
+ # Convenience functions using the main logger
185
+ _main_logger = get_logger()
186
+
187
+
188
+ def debug(msg, *args, **kwargs):
189
+ """Log debug message"""
190
+ _main_logger.debug(msg, *args, **kwargs)
191
+
192
+
193
+ def info(msg, *args, **kwargs):
194
+ """Log info message"""
195
+ _main_logger.info(msg, *args, **kwargs)
196
+
197
+
198
+ def progress_log(msg, *args, **kwargs) -> None:
199
+ """Log progress message"""
200
+ # Preserve dedicated PROGRESS level without monkey-patching Logger
201
+ _main_logger.log(PROGRESS_LEVEL, msg, *args, **kwargs)
202
+
203
+
204
+ def warning(msg, *args, **kwargs):
205
+ """Log warning message"""
206
+ _main_logger.warning(msg, *args, **kwargs)
207
+
208
+
209
+ def error(msg, *args, **kwargs):
210
+ """Log error message"""
211
+ _main_logger.error(msg, *args, **kwargs)
212
+
213
+
214
+ def critical(msg, *args, **kwargs):
215
+ """Log critical message"""
216
+ _main_logger.critical(msg, *args, **kwargs)
217
+
218
+
219
+ # For backward compatibility
220
+ minimal = info # Alias minimal to info
221
+ verbose = debug # Alias verbose to debug
222
+ progress: Callable[..., None] = progress_log # Alias for convenience
223
+
224
+
225
+ def get_hook_logger(name: str = "HookManager") -> logging.Logger:
226
+ """
227
+ Get a logger specifically configured for hook operations.
228
+
229
+ Args:
230
+ name (str): Name of the logger. Defaults to 'HookManager'
231
+
232
+ Returns:
233
+ logging.Logger: Configured logger for hook operations
234
+ """
235
+ return get_logger(name)
236
+
237
+
238
+ def log_hook_execution(
239
+ hook_name: str, tool_name: str, execution_time: float, success: bool
240
+ ):
241
+ """
242
+ Log hook execution details for monitoring and debugging.
243
+
244
+ Args:
245
+ hook_name (str): Name of the hook that was executed
246
+ tool_name (str): Name of the tool the hook was applied to
247
+ execution_time (float): Time taken to execute the hook in seconds
248
+ success (bool): Whether the hook execution was successful
249
+ """
250
+ logger = get_hook_logger()
251
+ status = "SUCCESS" if success else "FAILED"
252
+ logger.info(
253
+ f"Hook {hook_name} for tool {tool_name}: {status} ({execution_time:.2f}s)"
254
+ )