Source code for tooluniverse.biotools_tool
"""bio.tools registry search with sensible matching.
bio.tools treats an unquoted multi-word filter as a loose match on any of its words,
so ``operation=Variant calling`` matched 4,165 tools (the exact EDAM operation has
1,015) and ``topic=Functional genomics`` likewise. Multi-word ``operation`` and
``topic`` values are therefore sent as exact phrases. Relevance ordering for text
queries is a separate, config-level default (``sort=score`` in the tool's fields).
bio.tools also ignores ``size`` and always answers with a page of 50 tools, five
times the documented default of 10. ``page`` and ``size`` are therefore applied
here: a window of ``size`` tools starting at ``(page - 1) * size`` is cut out of the
50-tool server pages that cover it.
"""
from typing import Any, Dict
from .base_rest_tool import BaseRESTTool
from .tool_registry import register_tool
[docs]
@register_tool("BioToolsRESTTool")
class BioToolsRESTTool(BaseRESTTool):
"""BaseRESTTool that sends multi-word EDAM operation/topic terms as phrases."""
_PHRASE_PARAMS = ("operation", "topic")
[docs]
def _build_params(self, args: Dict[str, Any]) -> Dict[str, Any]:
params = super()._build_params(args)
for key in self._PHRASE_PARAMS:
value = params.get(key)
if isinstance(value, str):
value = value.strip()
already_quoted = value.startswith('"') and value.endswith('"')
if " " in value and not already_quoted:
params[key] = f'"{value}"'
return params
_SERVER_PAGE = 50
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
props = self.tool_config.get("parameter", {}).get("properties", {})
if "size" not in props:
return super().run(arguments)
size = self._positive(arguments.get("size"), props["size"].get("default", 10))
size = min(size, self._SERVER_PAGE)
page = self._positive(arguments.get("page"), 1)
start = (page - 1) * size
first = start // self._SERVER_PAGE + 1
last = (start + size - 1) // self._SERVER_PAGE + 1
tools, result = [], None
for server_page in range(first, last + 1):
result = super().run({**arguments, "page": server_page, "size": size})
data = result.get("data")
if result.get("status") != "success" or not isinstance(data, dict):
return result
if "list" not in data:
return result
tools.extend(data["list"])
if not data.get("next"):
break
skip = start - (first - 1) * self._SERVER_PAGE
data = result["data"]
total = data.get("count") or 0
data["list"] = tools[skip : skip + size]
data["previous"] = f"?page={page - 1}&size={size}" if page > 1 else None
data["next"] = f"?page={page + 1}&size={size}" if start + size < total else None
result["count"] = len(data["list"])
return result
[docs]
@staticmethod
def _positive(value, default):
try:
return max(1, int(value))
except (TypeError, ValueError):
return int(default)