Update mcp/hpo.py
Browse files- mcp/hpo.py +59 -11
mcp/hpo.py
CHANGED
@@ -1,16 +1,64 @@
|
|
1 |
-
|
2 |
-
"""
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
"""
|
|
|
5 |
|
6 |
-
import httpx
|
7 |
-
from
|
|
|
8 |
|
9 |
-
|
|
|
|
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
async def get_hpo(term_id: str) -> Dict:
|
12 |
-
"""
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""MedGenesis – Human Phenotype Ontology (HPO) async helper.
|
3 |
+
|
4 |
+
Adds reliability & caching over the minimal original version.
|
5 |
+
|
6 |
+
Capabilities
|
7 |
+
~~~~~~~~~~~~
|
8 |
+
* `get_hpo(term_id)` – fetch full term JSON (`HP:XXXXXXX`), cached 24 h.
|
9 |
+
* `search_hpo(term)` – simple keyword search, returns top hit (id + name).
|
10 |
+
|
11 |
+
API docs: https://hpo.jax.org/api
|
12 |
"""
|
13 |
+
from __future__ import annotations
|
14 |
|
15 |
+
import httpx, asyncio
|
16 |
+
from functools import lru_cache
|
17 |
+
from typing import Dict, Optional, List
|
18 |
|
19 |
+
_BASE = "https://hpo.jax.org/api/hpo"
|
20 |
+
_TIMEOUT = 15
|
21 |
+
_HEADERS = {"User-Agent": "MedGenesis/1.0 (https://huggingface.co/spaces)"}
|
22 |
|
23 |
+
# ---------------------------------------------------------------------
|
24 |
+
# Internal helper with retry
|
25 |
+
# ---------------------------------------------------------------------
|
26 |
+
async def _get(url: str, *, retries: int = 3) -> Dict:
|
27 |
+
delay = 2
|
28 |
+
last: Optional[httpx.Response] = None
|
29 |
+
for _ in range(retries):
|
30 |
+
async with httpx.AsyncClient(timeout=_TIMEOUT, headers=_HEADERS) as cli:
|
31 |
+
last = await cli.get(url)
|
32 |
+
if last.status_code == 200:
|
33 |
+
return last.json()
|
34 |
+
await asyncio.sleep(delay)
|
35 |
+
delay *= 2
|
36 |
+
last.raise_for_status() # type: ignore
|
37 |
+
|
38 |
+
# ---------------------------------------------------------------------
|
39 |
+
# Public helpers (cached)
|
40 |
+
# ---------------------------------------------------------------------
|
41 |
+
@lru_cache(maxsize=512)
|
42 |
async def get_hpo(term_id: str) -> Dict:
|
43 |
+
"""Return JSON for `HP:NNNNNNN` (raises if not found)."""
|
44 |
+
return await _get(f"{_BASE}/term/{term_id}")
|
45 |
+
|
46 |
+
|
47 |
+
@lru_cache(maxsize=256)
|
48 |
+
async def search_hpo(term: str) -> Optional[Dict]:
|
49 |
+
"""Return top search hit (`id`, `name`) for free‑text query or None."""
|
50 |
+
data = await _get(f"{_BASE}/search/{term}")
|
51 |
+
hits: List[Dict] = data.get("_embedded", {}).get("terms", [])
|
52 |
+
return hits[0] if hits else None
|
53 |
+
|
54 |
+
# ---------------------------------------------------------------------
|
55 |
+
# CLI test
|
56 |
+
# ---------------------------------------------------------------------
|
57 |
+
if __name__ == "__main__":
|
58 |
+
async def _demo():
|
59 |
+
hit = await search_hpo("microcephaly")
|
60 |
+
print("Top search hit:", hit)
|
61 |
+
if hit:
|
62 |
+
full = await get_hpo(hit["id"])
|
63 |
+
print("Definition:", full.get("definition"))
|
64 |
+
asyncio.run(_demo())
|