File size: 3,012 Bytes
b1e751b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""Fetching 3D protein structures: experimental structures UniProt
cross-references (RCSB PDB) when available, otherwise the public AlphaFold
DB's predicted model."""

from __future__ import annotations

from dataclasses import dataclass

import requests

UNIPROT_ENTRY_URL = "https://rest.uniprot.org/uniprotkb/{accession}.json"
ALPHAFOLD_PREDICTION_URL = "https://alphafold.ebi.ac.uk/api/prediction/{accession}"
RCSB_PDB_URL = "https://files.rcsb.org/download/{pdb_id}.pdb"


@dataclass
class PdbCrossReference:
    pdb_id: str
    method: str | None
    resolution: str | None
    chains: str | None


@dataclass
class AlphaFoldPrediction:
    pdb_url: str
    mean_plddt: float | None
    model_created_date: str | None


def fetch_pdb_cross_references(accession: str, timeout: float = 10.0) -> list[PdbCrossReference]:
    """Experimental structures UniProt cross-references to RCSB PDB for
    `accession` (often empty — most proteins have no solved structure)."""
    url = UNIPROT_ENTRY_URL.format(accession=accession.strip())
    response = requests.get(url, timeout=timeout)
    response.raise_for_status()
    entry = response.json()

    references = []
    for xref in entry.get("uniProtKBCrossReferences", []):
        if xref.get("database") != "PDB":
            continue
        properties = {p["key"]: p["value"] for p in xref.get("properties", [])}
        references.append(
            PdbCrossReference(
                pdb_id=xref.get("id"),
                method=properties.get("Method"),
                resolution=properties.get("Resolution"),
                chains=properties.get("Chains"),
            )
        )
    return references


def fetch_alphafold_prediction(accession: str, timeout: float = 10.0) -> AlphaFoldPrediction | None:
    """Metadata for `accession`'s AlphaFold DB prediction (including the
    *current* model file URLs — AlphaFold DB periodically republishes
    models under a new version number, so this is fetched from the API
    rather than guessed/hardcoded), or None if it has no model."""
    url = ALPHAFOLD_PREDICTION_URL.format(accession=accession.strip())
    response = requests.get(url, timeout=timeout)
    if response.status_code == 404:
        return None
    response.raise_for_status()
    entries = response.json()
    if not entries:
        return None
    entry = entries[0]
    return AlphaFoldPrediction(
        pdb_url=entry["pdbUrl"],
        mean_plddt=entry.get("globalMetricValue"),
        model_created_date=entry.get("modelCreatedDate"),
    )


def fetch_structure_text(url: str, timeout: float = 20.0) -> str:
    """Raw PDB-format text from any public URL (AlphaFold DB or RCSB)."""
    response = requests.get(url, timeout=timeout)
    response.raise_for_status()
    return response.text


def fetch_pdb_structure(pdb_id: str, timeout: float = 20.0) -> str:
    """An experimental structure from RCSB PDB, as PDB text."""
    return fetch_structure_text(RCSB_PDB_URL.format(pdb_id=pdb_id.strip()), timeout=timeout)