| |
| """MedGenesis – **Open Targets** GraphQL helper (async, cached). |
| Updated version adds: |
| * Retry with exponential back‑off (2→4→8 s) |
| * 12‑h LRU cache (256 genes) |
| * Graceful empty‑list fallback & doc‑string examples. |
| """ |
| from __future__ import annotations |
|
|
| import asyncio, textwrap, httpx |
| from functools import lru_cache |
| from typing import List, Dict |
|
|
| _OT_URL = "https://api.platform.opentargets.org/api/v4/graphql" |
|
|
| _QUERY = textwrap.dedent( |
| """ |
| query Assoc($gene: String!, $size: Int!) { |
| associations(geneSymbol: $gene, size: $size) { |
| rows { |
| score |
| datatypeId |
| datasourceId |
| disease { id name } |
| target { id symbol } |
| } |
| } |
| } |
| """ |
| ) |
|
|
| async def _post(payload: Dict, retries: int = 3) -> Dict: |
| """POST with back‑off retry (2×, 4×); returns JSON.""" |
| delay = 2 |
| last_resp = None |
| for _ in range(retries): |
| async with httpx.AsyncClient(timeout=15) as cli: |
| last_resp = await cli.post(_OT_URL, json=payload) |
| if last_resp.status_code == 200: |
| return last_resp.json() |
| await asyncio.sleep(delay) |
| delay *= 2 |
| |
| last_resp.raise_for_status() |
|
|
|
|
| @lru_cache(maxsize=256) |
| async def fetch_ot_associations(gene_symbol: str, *, size: int = 30) -> List[Dict]: |
| """Return association rows for *gene_symbol* (or []). Example:: |
| |
| rows = await fetch_ot_associations("TP53", size=20) |
| """ |
| payload = {"query": _QUERY, "variables": {"gene": gene_symbol, "size": size}} |
| data = await _post(payload) |
| return data.get("data", {}).get("associations", {}).get("rows", []) |
|
|