Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """FastAPI research API for the MSC Aging Agent Corpus (Hugging Face Space).""" | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from contextlib import asynccontextmanager | |
| from typing import Any | |
| import pandas as pd | |
| from fastapi import FastAPI, HTTPException, Query | |
| from pydantic import BaseModel, Field | |
| from msc_corpus import connect | |
| DEFAULT_REVISION = os.environ.get("CORPUS_REVISION", "corpus-v2026.06.6") | |
| CACHE_DIR = os.environ.get("CORPUS_CACHE_DIR", "/tmp/msc_corpus_cache") | |
| corpus = None | |
| async def lifespan(_app: FastAPI): | |
| global corpus | |
| corpus = connect(remote_only=True, revision=DEFAULT_REVISION, cache_dir=CACHE_DIR) | |
| yield | |
| app = FastAPI( | |
| title="MSC Aging Corpus Research API", | |
| description=( | |
| "Query the Syndicate Laboratories MSC Aging Agent Corpus: biomarker search, " | |
| "sample-level expression, OLS statistical reruns, and APA citations. " | |
| "Data source: Hugging Face Dataset S4MPL3BI4S/msc-aging-agent-corpus." | |
| ), | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| class ResearchRequest(BaseModel): | |
| gene: str = Field(..., description="Gene symbol, e.g. NDRG1") | |
| dataset_id: str | None = Field(None, description="Optional GEO accession, e.g. GSE39540") | |
| species: str | None = Field(None, description="Optional species filter, e.g. Homo sapiens") | |
| fdr_only: bool = False | |
| actions: list[str] = Field( | |
| default=["search", "rerun", "cite"], | |
| description="Steps: search, expression, rerun, mechanism, cite", | |
| ) | |
| def _require_corpus(): | |
| if corpus is None: | |
| raise HTTPException(status_code=503, detail="Corpus not loaded yet.") | |
| return corpus | |
| def _records(frame: pd.DataFrame, limit: int = 500) -> dict[str, Any]: | |
| if frame.empty: | |
| return {"count": 0, "rows": []} | |
| trimmed = frame.head(limit) | |
| return { | |
| "count": int(len(frame)), | |
| "truncated": len(frame) > limit, | |
| "rows": json.loads(trimmed.to_json(orient="records")), | |
| } | |
| def root() -> dict[str, str]: | |
| return { | |
| "service": "MSC Aging Corpus Research API", | |
| "curator": "James Utley, PhD · Syndicate Laboratories", | |
| "dataset": "S4MPL3BI4S/msc-aging-agent-corpus", | |
| "revision": DEFAULT_REVISION, | |
| "openapi_docs": "/docs", | |
| "research_endpoint": "POST /research", | |
| } | |
| def health() -> dict[str, str]: | |
| _require_corpus() | |
| return {"status": "ok", "revision": DEFAULT_REVISION} | |
| def connection_manifest() -> dict[str, Any]: | |
| return _require_corpus().connection_info() | |
| def manifest( | |
| species: str | None = None, | |
| limit: int = Query(100, ge=1, le=500), | |
| ) -> dict[str, Any]: | |
| frame = _require_corpus().manifest() | |
| if species: | |
| frame = frame.loc[frame["species"] == species] | |
| return _records(frame, limit=limit) | |
| def search_gene( | |
| gene: str, | |
| species: str | None = None, | |
| dataset_id: str | None = None, | |
| fdr_only: bool = False, | |
| limit: int = Query(200, ge=1, le=1000), | |
| ) -> dict[str, Any]: | |
| frame = _require_corpus().search_gene( | |
| gene, | |
| species=species, | |
| dataset_id=dataset_id, | |
| fdr_only=fdr_only, | |
| ) | |
| return _records(frame, limit=limit) | |
| def expression( | |
| gene: str, | |
| dataset_id: str, | |
| limit: int = Query(500, ge=1, le=5000), | |
| ) -> dict[str, Any]: | |
| try: | |
| frame = _require_corpus().expression(gene, dataset_id) | |
| except Exception as exc: # noqa: BLE001 | |
| raise HTTPException(status_code=404, detail=str(exc)) from exc | |
| return _records(frame, limit=limit) | |
| def rerun_model(gene: str, dataset_id: str) -> dict[str, Any]: | |
| try: | |
| result = _require_corpus().rerun_model(gene, dataset_id) | |
| except ValueError as exc: | |
| raise HTTPException(status_code=404, detail=str(exc)) from exc | |
| result.pop("summary", None) | |
| return result | |
| def cite(gene: str, datasets: str = Query(..., description="Comma-separated GSE IDs")) -> dict[str, str]: | |
| used = [item.strip() for item in datasets.split(",") if item.strip()] | |
| return _require_corpus().cite_gene(gene, datasets_used=used) | |
| def research(body: ResearchRequest) -> dict[str, Any]: | |
| try: | |
| return _require_corpus().research( | |
| body.gene, | |
| dataset_id=body.dataset_id, | |
| species=body.species, | |
| actions=body.actions, | |
| fdr_only=body.fdr_only, | |
| ) | |
| except ValueError as exc: | |
| raise HTTPException(status_code=404, detail=str(exc)) from exc | |