Samad14's picture
Deploy prep: Render config + Dockerfile, refactored backend auth flow, dashboard, landing page, error states, guest flow
76fb273
Raw History Blame
2.6 kB
"""
Thin client for NCBI BLAST URL API (QBLAST).
Rate limit: NCBI enforces 1 request per 10 seconds without an API key,
3 req/s with an API key. Rate limiting is the caller's responsibility.
API docs: https://ncbi.github.io/blast-cloud/api.html
"""
import re
import httpx
from typing import Optional
NCBI_BLAST_URL = "https://blast.ncbi.nlm.nih.gov/blast/Blast.cgi"
RATE_LIMIT_SECONDS = 10
async def submit_blast(
sequence: str,
program: str = "blastp",
database: str = "nr",
hitlist_size: int = 100,
expect: float = 10.0,
gapopen: int = -1,
gapextend: int = -1,
matrix: str = "BLOSUM62",
async_flag: bool = True,
) -> dict:
params = {
"CMD": "Put",
"PROGRAM": program,
"DATABASE": database,
"QUERY": sequence,
"HITLIST_SIZE": str(hitlist_size),
"EXPECT": str(expect),
"MATRIX": matrix,
"ASYNC": "1" if async_flag else "0",
}
if gapopen > 0:
params["GAPOPEN"] = str(gapopen)
if gapextend > 0:
params["GAPEXTEND"] = str(gapextend)
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(NCBI_BLAST_URL, data=params)
resp.raise_for_status()
text = resp.text
rid_match = re.search(r"RID\s*=\s*(\S+)", text)
rtoe_match = re.search(r"RTOE\s*=\s*(\d+)", text)
if not rid_match:
return {"error": "No RID returned from NCBI", "raw": text[:500]}
rid = rid_match.group(1)
rtoe = int(rtoe_match.group(1)) if rtoe_match else 60
return {"rid": rid, "estimated_seconds": rtoe}
async def check_status(rid: str, fmt: str = "XML") -> dict:
params = {"CMD": "Get", "FORMAT_TYPE": fmt, "RID": rid}
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.get(NCBI_BLAST_URL, params=params)
resp.raise_for_status()
text = resp.text
if "Status=" in text:
status_match = re.search(r"Status\s*=\s*(\w+)", text)
status = status_match.group(1) if status_match else "UNKNOWN"
else:
status = "READY"
return {"status": status, "raw": text, "rid": rid}
async def fetch_results(rid: str, fmt: str = "XML") -> dict:
params = {"CMD": "Get", "FORMAT_TYPE": fmt, "RID": rid}
async with httpx.AsyncClient(timeout=60) as client:
resp = await client.get(NCBI_BLAST_URL, params=params)
resp.raise_for_status()
text = resp.text
if "Status=" in text and "Status=READY" not in text:
return {"error": "Results not ready", "raw": text[:200]}
return {"raw": text, "rid": rid}