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
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
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
# 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).