tooluniverse.modomics_tool 源代码
"""
MODOMICS API tool for ToolUniverse.
MODOMICS is a comprehensive database of RNA modifications providing
chemical structures, biosynthetic pathways, locations in RNA sequences,
and modifying enzymes.
API: https://iimcb.genesilico.pl/modomics/api/
No authentication required.
"""
import requests
from typing import Dict, Any
from .base_tool import BaseTool
from .tool_registry import register_tool
MODOMICS_BASE = "https://iimcb.genesilico.pl/modomics/api"
[文档]
@register_tool("MODOMICSTool")
class MODOMICSTool(BaseTool):
"""
Tool for querying the MODOMICS RNA modification database.
Supports: list_modifications, get_modification, search_modifications.
No authentication required.
"""
[文档]
def __init__(self, tool_config: Dict[str, Any]):
super().__init__(tool_config)
self.timeout = tool_config.get("timeout", 30)
self.endpoint_type = tool_config.get("fields", {}).get(
"endpoint_type", "list_modifications"
)
[文档]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute the MODOMICS API call."""
try:
return self._query(arguments)
except requests.exceptions.Timeout:
return {
"status": "error",
"error": f"MODOMICS API timed out after {self.timeout}s",
}
except requests.exceptions.ConnectionError:
return {
"status": "error",
"error": "Failed to connect to MODOMICS API.",
}
except Exception as e:
return {
"status": "error",
"error": f"Error querying MODOMICS: {str(e)}",
}
[文档]
def _query(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Route to the appropriate endpoint."""
dispatch = {
"list_modifications": self._list_modifications,
"get_modification": self._get_modification,
"search_modifications": self._search_modifications,
}
handler = dispatch.get(self.endpoint_type)
if not handler:
return {
"status": "error",
"error": f"Unknown endpoint type: {self.endpoint_type}",
}
return handler(arguments)
[文档]
def _list_modifications(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""List all RNA modifications in MODOMICS."""
limit = arguments.get("limit", 50)
url = f"{MODOMICS_BASE}/modifications"
resp = requests.get(url, timeout=self.timeout)
if resp.status_code != 200:
return {
"status": "error",
"error": f"API returned {resp.status_code}: {resp.text[:200]}",
}
data = resp.json()
# MODOMICS returns a dict keyed by ID
modifications = []
for mod_id, mod_data in list(data.items())[:limit]:
modifications.append(
{
"id": mod_data.get("id"),
"name": mod_data.get("name"),
"short_name": mod_data.get("short_name"),
"formula": mod_data.get("formula"),
"mass_avg": mod_data.get("mass_avg"),
"mass_monoiso": mod_data.get("mass_monoiso"),
"reference_moiety": mod_data.get("reference_moiety"),
"smiles": mod_data.get("smile"),
}
)
return {
"status": "success",
"data": {
"modifications": modifications,
"total": len(data),
"returned": len(modifications),
},
}
[文档]
def _get_modification(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Get details of a specific RNA modification by ID."""
mod_id = arguments.get("modification_id")
if not mod_id:
return {
"status": "error",
"error": "modification_id parameter is required",
}
url = f"{MODOMICS_BASE}/modifications/{mod_id}"
resp = requests.get(url, timeout=self.timeout)
if resp.status_code != 200:
return {
"status": "error",
"error": f"API returned {resp.status_code}: {resp.text[:200]}",
}
data = resp.json()
# Response is a dict keyed by ID, even for single item
if not data:
return {
"status": "error",
"error": f"No modification found with ID {mod_id}",
}
mod_data = list(data.values())[0]
return {
"status": "success",
"data": {
"id": mod_data.get("id"),
"name": mod_data.get("name"),
"short_name": mod_data.get("short_name"),
"new_abbrev": mod_data.get("new_abbrev"),
"formula": mod_data.get("formula"),
"mass_avg": mod_data.get("mass_avg"),
"mass_monoiso": mod_data.get("mass_monoiso"),
"mass_prot": mod_data.get("mass_prot"),
"reference_moiety": mod_data.get("reference_moiety"),
"smiles": mod_data.get("smile"),
"product_ions": mod_data.get("product_ions"),
"lc_elution_comment": mod_data.get("lc_elution_comment"),
"lc_elution_time": mod_data.get("lc_elution_time"),
},
}
[文档]
def _search_modifications(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Search RNA modifications by name or short name."""
query = arguments.get("query", "")
if not query:
return {"status": "error", "error": "query parameter is required"}
limit = arguments.get("limit", 20)
url = f"{MODOMICS_BASE}/modifications"
resp = requests.get(url, timeout=self.timeout)
if resp.status_code != 200:
return {
"status": "error",
"error": f"API returned {resp.status_code}: {resp.text[:200]}",
}
data = resp.json()
query_lower = query.lower()
# Client-side search across name, short_name, formula
matches = []
for mod_data in data.values():
name = (mod_data.get("name") or "").lower()
short = (mod_data.get("short_name") or "").lower()
formula = (mod_data.get("formula") or "").lower()
if query_lower in name or query_lower in short or query_lower in formula:
matches.append(
{
"id": mod_data.get("id"),
"name": mod_data.get("name"),
"short_name": mod_data.get("short_name"),
"formula": mod_data.get("formula"),
"mass_avg": mod_data.get("mass_avg"),
"reference_moiety": mod_data.get("reference_moiety"),
"smiles": mod_data.get("smile"),
}
)
return {
"status": "success",
"data": {
"results": matches[:limit],
"total_matches": len(matches),
"returned": min(len(matches), limit),
},
}