bio-nexus-api / app /tools /uniprot.py
Samad14's picture
fix(blast): poll cap 65min, DNA validation, program/db/max_hits params
cd0c7a9 verified
Raw
History Blame Contribute Delete
5.3 kB
import re
import httpx
from typing import Any
from app.tools.base import BaseTool
from app.config import settings
from app.services.cache import ttl_cache
class UniprotTool(BaseTool):
name = "uniprot"
@ttl_cache(ttl=86400, prefix="uniprot")
async def run(self, input: dict) -> dict:
accession = input.get("accession", "").strip().upper()
if not accession:
return {"error": "No accession provided"}
data = await self._fetch(accession)
if "error" in data:
return data
return {
"accession": data.get("primaryAccession", ""),
"full_name": self._extract_name(data),
"ec_number": (data.get("proteinDescription", {}) or {}).get("ecNumbers", [{}])[0].get("ecNumber", "") if data.get("proteinDescription") else "",
"gene_names": [g.get("geneName", {}).get("value", "") for g in (data.get("genes") or []) if g.get("geneName")],
"organism": ((data.get("organism", {}) or {}).get("scientificName", "")),
"functions": self._extract_functions(data),
"keywords": [kw.get("name", "") for kw in (data.get("keywords") or [])],
"sequence": (data.get("sequence", {}) or {}).get("value", ""),
"sequence_length": ((data.get("sequence", {}) or {}).get("length", 0)),
"subcellular_locations": self._extract_locations(data),
"pdb_ids": self._extract_pdb(data),
"features": self._extract_features(data),
"go_terms": self._extract_go_terms(data),
"cds_accessions": self._extract_cds_accessions(data),
}
async def _fetch(self, accession: str) -> dict:
accession = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', accession)
url = f"{settings.UNIPROT_BASE_URL}/{accession}"
async with httpx.AsyncClient(timeout=15) as client:
resp = await client.get(url, params={"format": "json"})
if resp.status_code == 404:
return {"error": f"Accession {accession} not found"}
if resp.status_code >= 400:
return {"error": f"UniProt returned {resp.status_code} for {accession}"}
return resp.json()
def _extract_name(self, data: dict) -> str:
desc = data.get("proteinDescription", {}) or {}
rec_name = desc.get("recommendedName", {}) or {}
return (rec_name.get("fullName", {}) or {}).get("value", "")
def _extract_functions(self, data: dict) -> list[str]:
comments = data.get("comments") or []
funcs = []
for c in comments:
if c.get("commentType") == "FUNCTION":
texts = c.get("texts") or []
for t in texts:
val = (t.get("value") or "").strip()
if val:
funcs.append(val)
return funcs
def _extract_locations(self, data: dict) -> list[str]:
comments = data.get("comments") or []
locs = []
for c in comments:
if c.get("commentType") == "SUBCELLULAR_LOCATION":
subcels = c.get("subcellularLocations") or []
for s in subcels:
loc = (s.get("location", {}) or {}).get("value", "")
if loc:
locs.append(loc)
return locs
def _extract_cds_accessions(self, data: dict) -> list[dict]:
refs = data.get("uniProtKBCrossReferences") or []
cds = []
seen_ids = set()
for r in refs:
db = r.get("database", "")
if db in ("EMBL", "GenBank", "DDBJ"):
props = {p.get("key", ""): p.get("value", "") for p in (r.get("properties") or [])}
acc = r.get("id", "")
if acc and acc not in seen_ids:
seen_ids.add(acc)
cds.append({
"database": db,
"accession": acc,
"protein_sequence_id": props.get("protein sequence ID", ""),
"nucleotide_sequence_id": props.get("nucleotide sequence ID", ""),
})
return cds
def _extract_pdb(self, data: dict) -> list[str]:
refs = data.get("uniProtKBCrossReferences") or []
pdbs = []
for r in refs:
if r.get("database") == "PDB":
pdbs.append(r.get("id", ""))
return pdbs
def _extract_features(self, data: dict) -> list[dict]:
features = data.get("features") or []
result = []
for f in features:
result.append({
"type": f.get("type", ""),
"description": f.get("description", ""),
"begin": (f.get("location", {}) or {}).get("start", {}).get("value"),
"end": (f.get("location", {}) or {}).get("end", {}).get("value"),
})
return result
def _extract_go_terms(self, data: dict) -> list[str]:
refs = data.get("uniProtKBCrossReferences") or []
go = []
for r in refs:
if r.get("database") == "GO":
term = r.get("properties", [{}])[0].get("value", "") if r.get("properties") else ""
if term:
go.append(term)
return go