Source code for tooluniverse.bar_tool

# bar_tool.py
"""
BAR (Bio-Analytic Resource for Plant Biology) tools for ToolUniverse.

The BAR (bar.utoronto.ca), a Global Core Biodata Resource, is the
standard resource for Arabidopsis and other plant species' gene
annotation and expression data -- something ToolUniverse's existing
plant tool (plant_reactome_tool.py, pathway-level only) has no
equivalent for. It publishes a documented, unauthenticated OpenAPI
spec at /api/swagger.json; this wraps two of its endpoints: per-gene
annotation/position/aliases, and RNA-seq expression (including
single-cell cluster-level expression means).

API: https://bar.utoronto.ca/api
No authentication required.
"""

from typing import Any, Dict

import requests

from .base_tool import BaseTool
from .tool_registry import register_tool

BAR_API_URL = "https://bar.utoronto.ca/api"


def _bar_get(url: str, timeout: int):
    """GET a BAR endpoint, returning (payload, error_envelope).

    Exactly one of the two is non-None; the error envelope is the standard
    {"status": "error", ...} dict so callers never raise.
    """
    try:
        resp = requests.get(url, timeout=timeout)
    except requests.exceptions.Timeout:
        return None, {"status": "error", "error": f"BAR request timed out after {timeout}s"}
    except requests.exceptions.RequestException as e:
        return None, {"status": "error", "error": f"BAR request failed: {e}"}

    if resp.status_code == 400:
        try:
            detail = resp.json().get("error")
        except ValueError:
            detail = None
        return None, {"status": "error", "error": detail or "BAR rejected the request."}
    resp.raise_for_status()
    try:
        payload = resp.json()
    except ValueError:
        return None, {"status": "error", "error": "BAR returned a non-JSON response."}
    if not payload.get("wasSuccessful", True):
        return None, {
            "status": "error",
            "error": payload.get("error") or "BAR reported an unsuccessful request.",
        }
    return payload, None


[docs] @register_tool("BARTool") class BARTool(BaseTool): """ Tool for querying the BAR (Bio-Analytic Resource for Plant Biology), dispatched by fields.operation: - "get_gene_info" : gene position, strand, aliases, annotation - "get_rnaseq_expression" : RNA-seq expression (bulk or single-cell cluster means) for a gene No authentication required. """
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 30) self.operation = tool_config.get("fields", {}).get( "operation", "get_gene_info" )
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: if self.operation == "get_gene_info": return self._get_gene_info(arguments) if self.operation == "get_rnaseq_expression": return self._get_rnaseq_expression(arguments) return {"status": "error", "error": f"Unknown operation: {self.operation}"}
[docs] def _get_gene_info(self, arguments: Dict[str, Any]) -> Dict[str, Any]: species = (arguments.get("species") or "arabidopsis").strip() gene_id = (arguments.get("gene_id") or "").strip() if not gene_id: return { "status": "error", "error": "gene_id is required, e.g. 'AT1G01010'.", } payload, err = _bar_get( f"{BAR_API_URL}/gene_information/single_gene_query/{species}/{gene_id}", self.timeout, ) if err is not None: return err record = (payload.get("data") or {}).get(gene_id.upper()) or next( iter((payload.get("data") or {}).values()), None ) if not record: return { "status": "error", "error": f"No BAR gene record found for '{gene_id}' in '{species}'.", } return { "status": "success", "data": record, "metadata": {"species": species, "gene_id": gene_id, "source": "BAR (bar.utoronto.ca)"}, }
[docs] def _get_rnaseq_expression(self, arguments: Dict[str, Any]) -> Dict[str, Any]: species = (arguments.get("species") or "arabidopsis").strip() database = (arguments.get("database") or "single_cell").strip() gene_id = (arguments.get("gene_id") or "").strip() if not gene_id: return { "status": "error", "error": "gene_id is required, e.g. 'At1g01010'.", } payload, err = _bar_get( f"{BAR_API_URL}/rnaseq_gene_expression/{species}/{database}/{gene_id}", self.timeout, ) if err is not None: return err expression = payload.get("data") or {} return { "status": "success", "data": expression, "metadata": { "species": species, "database": database, "gene_id": gene_id, "sample_count": len(expression), "source": "BAR (bar.utoronto.ca)", }, }