Source code for tooluniverse.interpro_tool
import requests
from typing import Any, Dict
from .base_tool import BaseTool
from .tool_registry import register_tool
[docs]
def _json_or_none(response):
"""Parse a JSON response body, returning None when there is no body.
The InterPro API reports an empty result set as HTTP 204 No Content with a
zero-length body instead of a 200 carrying ``{"count": 0, "results": []}``.
``raise_for_status()`` treats 204 as success, so calling ``.json()`` on it
raises a JSONDecodeError that used to surface as an opaque
"InterPro API error: Expecting value: line 1 column 1 (char 0)" -- callers
could not tell "nothing matched" from "the tool is broken".
"""
if response.status_code == 204:
return None
body = response.content
if not body or not body.strip():
return None
return response.json()
[docs]
@register_tool("InterProRESTTool")
class InterProRESTTool(BaseTool):
[docs]
def __init__(self, tool_config: Dict):
super().__init__(tool_config)
self.base_url = "https://www.ebi.ac.uk/interpro/api"
self.session = requests.Session()
self.session.headers.update(
{"Accept": "application/json", "User-Agent": "ToolUniverse/1.0"}
)
self.timeout = 30
[docs]
def _build_url(self, args: Dict[str, Any]) -> str:
"""Build URL from endpoint template and arguments.
Applies schema defaults for any template placeholders not in args.
"""
url = self.tool_config["fields"]["endpoint"]
# Merge schema defaults for any missing template parameters
merged = dict(args)
props = self.tool_config.get("parameter", {}).get("properties", {})
for k, schema in props.items():
if k not in merged and "default" in schema:
merged[k] = schema["default"]
for k, v in merged.items():
url = url.replace(f"{{{k}}}", str(v))
return url
[docs]
def _extract_data(self, data: Dict, extract_path: str = None) -> Any:
"""Extract specific data from API response"""
if not extract_path:
return data
# Handle specific InterPro extraction patterns
if extract_path == "results":
return data.get("results", [])
elif extract_path == "count":
return data.get("count", 0)
elif extract_path == "metadata":
return data.get("metadata", {})
return data
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute the InterPro API call"""
url = ""
try:
url = self._build_url(arguments)
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
# HTTP 204 / empty body == "nothing matched". Fall through with an
# empty payload so extraction yields the normal empty shape
# ([] for extract_path "results", 0 for "count") instead of
# letting .json() raise.
payload = _json_or_none(response)
empty = payload is None
data = {} if empty else payload
extract_path = self.tool_config["fields"].get("extract_path")
if extract_path:
result = self._extract_data(data, extract_path)
else:
result = data
if empty:
count = 0
else:
count = len(result) if isinstance(result, list) else 1
return {
"status": "success",
"data": result,
"url": url,
"count": count,
}
except Exception as e:
return {
"status": "error",
"error": f"InterPro API error: {str(e)}",
"url": url,
}