Structural Bioinformatics

Lesson 3 of 13 · 12 min

Fetching and Inspecting Structures

Reproducible structural analysis begins with fetching coordinates programmatically rather than clicking download buttons. Biopython, the RCSB and PDBe REST APIs, and PyMOL each expose the Protein Data Bank, and each also lets you inspect resolution, entities, bound ligands, and unmodeled residues before you commit to a structure. One choice matters more than the download method itself: whether you want the asymmetric unit that was deposited or the biological assembly that represents the functional molecule.

Fetching With Biopython

python
from Bio.PDB import PDBList, MMCIFParser
from Bio.PDB.MMCIF2Dict import MMCIF2Dict

# Downloads 4hhb.cif into ./structures and returns the local path
pdbl = PDBList()
path = pdbl.retrieve_pdb_file("4HHB", pdir="structures", file_format="mmCif")

structure = MMCIFParser(QUIET=True).get_structure("4HHB", path)
model = structure[0]
mmcif = MMCIF2Dict(path)

print("chains:", [c.id for c in model])
print("resolution:", mmcif["_refine.ls_d_res_high"][0], "A")

# Molecular entities: controlled-vocabulary type plus description
for etype, desc in zip(mmcif["_entity.type"], mmcif["_entity.pdbx_description"]):
    print(f"  entity {etype:11s} {desc}")

# Bound ligands = hetero residues that are not water (hetflag starts with 'H_')
ligands = sorted({r.resname for r in model.get_residues() if r.id[0].startswith("H_")})
print("ligands:", ligands)

# Unmodeled residues: mmCIF category populated from REMARK 465 (absent -> [])
missing = mmcif.get("_pdbx_unobs_or_zero_occ_residues.auth_comp_id", [])
print("missing residues:", len(missing))
chains: ['A', 'B', 'C', 'D']
resolution: 1.74 A
  entity polymer     Hemoglobin subunit alpha
  entity polymer     Hemoglobin subunit beta
  entity non-polymer PROTOPORPHYRIN IX CONTAINING FE
  entity non-polymer PHOSPHATE ION
  entity water       water
ligands: ['HEM', 'PO4']
missing residues: 0

RCSB And PDBe REST APIs

python
import requests

BASE = "https://data.rcsb.org/rest/v1/core"

# RCSB Data API: aggregated metadata for a PDB entry
e = requests.get(f"{BASE}/entry/4HHB", timeout=30).json()
info = e["rcsb_entry_info"]
print("method:", e["exptl"][0]["method"])
print("resolution:", info["resolution_combined"])        # list of values
print("assemblies:", info["assembly_count"])
print("nonpolymer entities:", info["nonpolymer_entity_count"])

# Resolve each non-polymer entity to its chem-comp id (populated for every entry;
# the rcsb_entry_info.nonpolymer_bound_components shortcut drops non-contacting ions)
ligands = []
for eid in e["rcsb_entry_container_identifiers"]["non_polymer_entity_ids"]:
    ne = requests.get(f"{BASE}/nonpolymer_entity/4HHB/{eid}", timeout=30).json()
    ligands.append(ne["pdbx_entity_nonpoly"]["comp_id"])
print("ligands:", ligands)

# PDBe Data API keys its results by the lowercase 4-character id
s = requests.get("https://www.ebi.ac.uk/pdbe/api/pdb/entry/summary/4hhb", timeout=30).json()
entry = s["4hhb"][0]
print("title:", entry["title"])
print("assemblies (PDBe):", len(entry["assemblies"]))

# Download the FIRST biological assembly as mmCIF (not the asymmetric unit)
r = requests.get("https://files.rcsb.org/download/4hhb-assembly1.cif", timeout=60)
with open("4hhb-assembly1.cif", "wb") as fh:
    fh.write(r.content)
method: X-RAY DIFFRACTION
resolution: [1.74]
assemblies: 1
nonpolymer entities: 2
ligands: ['HEM', 'PO4']
title: THE CRYSTAL STRUCTURE OF HUMAN DEOXYHAEMOGLOBIN AT 1.74 ANGSTROMS RESOLUTION
assemblies (PDBe): 1

Assembly Versus Asymmetric Unit

python
# Run with: pymol -cq assembly.py
from pymol import cmd

# Asymmetric unit: exactly the deposited protomer of HIV-1 protease
cmd.fetch("1hhp", "asu", type="pdb")
print("ASU chains:", cmd.get_chains("asu"))
print("ASU atoms:", cmd.count_atoms("asu"))

# Biological assembly 1: the functional homodimer, built from mmCIF operators
# (set the 'assembly' setting BEFORE loading; the legacy route is type=pdb1)
cmd.set("assembly", "1")
cmd.fetch("1hhp", "bio", type="cif")
print("assembly atoms:", cmd.count_atoms("bio"))
ASU chains: ['A']
ASU atoms: 758
assembly atoms: 1516

Warning: The asymmetric unit is what crystallography deposited; the biological assembly is the functional form of the molecule. Computing interfaces, buried surface area, or oligomeric state on the asymmetric unit gives wrong answers whenever the two differ, as with the HIV-1 protease dimer above. Fetch assemblies explicitly: files.rcsb.org/download/PDBID-assembly1.cif or the legacy PDBID.pdb1, PyMOL fetch PDBID, type=pdb1, or set assembly, 1 before loading a cif.

Try it yourself: Fetch 1STP in PyMOL as both the asymmetric unit (type=pdb) and biological assembly 1 (set assembly, 1 then fetch as cif), and confirm the assembly is the streptavidin homotetramer by comparing cmd.count_atoms (the assembly is about four times the asymmetric unit). Then query https://data.rcsb.org/rest/v1/core/entry/1STP, read resolution_combined, and resolve each id in rcsb_entry_container_identifiers.non_polymer_entity_ids through the nonpolymer_entity endpoint to confirm the bound ligand is biotin (BTN).