Source code for tooluniverse.ewas_catalog_tool

# ewas_catalog_tool.py
"""
EWAS Catalog tool for ToolUniverse.

The EWAS Catalog (MRC-IEU, Bristol) aggregates published epigenome-wide
association study results: which CpG sites' methylation is associated with
which trait, in which tissue, cohort, and effect size. ToolUniverse has no
methylation-association layer at all today, only GWAS-style variant
association (GWAS Catalog, PheWAS).

The API's `trait` search has no result cap and returns its entire match set
in one response; a broad query like trait='smoking' took ~80s and 22 MB in
testing. This tool exposes only `cpg` and `gene` search, both single-digit-
seconds even for heavily studied genes, and truncates client-side.

API: http://ewascatalog.org/api/
No authentication required.
"""

from typing import Dict, Any, List, Optional

import requests

from .base_tool import BaseTool
from .tool_registry import register_tool

EWAS_CATALOG_URL = "http://ewascatalog.org/api/"

_NUMERIC_FIELDS = {"p", "beta", "se"}
_INT_FIELDS = {"n", "n_cohorts"}


def _coerce(field: str, value: Any) -> Any:
    """Convert the catalog's string-typed numeric fields."""
    if value is None or value == "":
        return None
    if field in _NUMERIC_FIELDS:
        try:
            return float(value)
        except (TypeError, ValueError):
            return value
    if field in _INT_FIELDS:
        try:
            return int(value)
        except (TypeError, ValueError):
            return value
    return value


[docs] @register_tool("EWASCatalogTool") class EWASCatalogTool(BaseTool): """ Tool for querying the EWAS Catalog of epigenome-wide association results. Supports looking up all published associations for a CpG site or a gene, ranked by significance. No authentication required. """
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 60) self.operation = tool_config.get("fields", {}).get( "operation", "search_by_cpg" )
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Execute the EWAS Catalog lookup.""" try: if self.operation == "search_by_cpg": return self._search(arguments, "cpg", "cpg_id") if self.operation == "search_by_gene": return self._search(arguments, "gene", "gene_symbol") return { "status": "error", "error": f"Unknown operation: {self.operation}", } except requests.exceptions.Timeout: return { "status": "error", "error": f"EWAS Catalog request timed out after {self.timeout}s. " "Heavily studied genes (e.g. AHRR, F2RL3) can be slow.", } except requests.exceptions.ConnectionError: return { "status": "error", "error": "Failed to connect to the EWAS Catalog. Check network.", } except requests.exceptions.HTTPError as e: code = e.response.status_code if e.response is not None else "unknown" return { "status": "error", "error": f"EWAS Catalog returned HTTP {code}", } except ValueError: return { "status": "error", "error": "EWAS Catalog returned a non-JSON response", } except Exception as e: return { "status": "error", "error": f"Error querying EWAS Catalog: {str(e)}", }