Source code for tooluniverse.gsa_tool

# gsa_tool.py
"""
GSA (Genome Sequence Archive) tool for ToolUniverse.

GSA (ngdc.cncb.ac.cn/gsa), run by China's National Genomics Data Center
(NGDC/CNCB), is a major raw-sequencing-data archive alongside NCBI's SRA,
EBI's ENA, and Japan's DDBJ (the latter already covered by
ddbj_tool.py) -- but for a large and growing share of Chinese-origin
genomic datasets, GSA is the primary or only archive. There is no JSON
API; accession pages are server-rendered HTML (Chinese-labeled fields),
so this tool parses that page for a given CRA-format run/study
accession, returning its title, associated BioProject, publication
link, file count/size, and direct HTTPS/FTP download URLs.

No authentication required.
"""

from typing import Any, Dict, Optional

import requests
from bs4 import BeautifulSoup

from .base_tool import BaseTool
from .tool_registry import register_tool

GSA_BASE_URL = "https://ngdc.cncb.ac.cn/gsa"
_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; ToolUniverse/1.0)"}


def _panel_by_heading(soup: BeautifulSoup, heading_text: str):
    for panel in soup.find_all("div", class_="panel-heading"):
        if heading_text in panel.get_text():
            return panel.find_parent("div", class_="panel")
    return None


def _label_value(soup: BeautifulSoup, label: str) -> Optional[str]:
    b = soup.find("b", string=lambda s: s and label in s)
    if not b:
        return None
    return b.parent.get_text(strip=True).replace(label, "").strip() or None


def _publication_info(soup: BeautifulSoup) -> Dict[str, Optional[str]]:
    panel = _panel_by_heading(soup, "出版信息")
    fields = {"title": None, "journal": None, "year": None, "doi": None, "pubmed_id": None}
    if panel is None:
        return fields
    label_map = {
        "文章标题": "title",
        "杂志名称": "journal",
        "发表年份": "year",
        "Doi": "doi",
        "PubMed ID": "pubmed_id",
    }
    for row in panel.find_all("div", class_="row"):
        strong = row.find("strong")
        if not strong:
            continue
        key = label_map.get(strong.get_text(strip=True))
        if key is None:
            continue
        divs = row.find_all("div")
        if divs:
            fields[key] = divs[-1].get_text(strip=True) or None
    return fields


def _download_urls(soup: BeautifulSoup) -> Dict[str, Optional[str]]:
    panel = _panel_by_heading(soup, "数据下载")
    urls = {"https": None, "ftp": None}
    if panel is None:
        return urls
    https_a = panel.find("a", href=lambda h: h and h.startswith("https://download.cncb.ac.cn"))
    ftp_a = panel.find("a", href=lambda h: h and h.startswith("ftp://"))
    urls["https"] = https_a.get("href") if https_a else None
    urls["ftp"] = ftp_a.get("href") if ftp_a else None
    return urls


[docs] @register_tool("GSATool") class GSATool(BaseTool): """Look up a GSA (Genome Sequence Archive) accession's metadata page. No authentication required. """
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 30)
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: accession = (arguments.get("accession") or "").strip().upper() if not accession: return { "status": "error", "error": "accession is required, e.g. 'CRA002926'.", } try: resp = requests.get( f"{GSA_BASE_URL}/browse/{accession}", headers=_HEADERS, timeout=self.timeout, ) except requests.exceptions.Timeout: return { "status": "error", "error": f"GSA request timed out after {self.timeout}s", } except requests.exceptions.RequestException as e: return {"status": "error", "error": f"GSA request failed: {e}"} resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") title = _label_value(soup, "标题:") if title is None: return { "status": "error", "error": f"No GSA accession found for '{accession}'.", } bioproject_b = soup.find("b", string=lambda s: s and "项目编号" in s) bioproject_a = bioproject_b.parent.find("a") if bioproject_b else None return { "status": "success", "data": { "accession": accession, "title": title, "bioproject_accession": ( bioproject_a.get_text(strip=True) if bioproject_a else None ), "bioproject_url": bioproject_a.get("href") if bioproject_a else None, "release_date": _label_value(soup, "发布日期:"), "file_count": _label_value(soup, "文件个数:"), "file_size": _label_value(soup, "文件大小:"), "publication": _publication_info(soup), "download_urls": _download_urls(soup), }, "metadata": { "accession": accession, "source": "GSA / National Genomics Data Center (ngdc.cncb.ac.cn)", }, }