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.
- 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 +544 -168
- 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 +82 -58
- 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-0.1.4.dist-info → tooluniverse-1.0.0.dist-info}/WHEEL +1 -1
- tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
- tooluniverse-0.1.4.dist-info/METADATA +0 -141
- tooluniverse-0.1.4.dist-info/RECORD +0 -18
- {tooluniverse-0.1.4.dist-info → tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
)
|