pdb-database · diff

git:20260428.81ea2d2 to git:20260522.6a6fd41

540 added, 369 removed. Audit A to A.

---
name: "pdb-database"
- description: "Query RCSB PDB (200K+ structures) via rcsb-api SDK. Text/attribute/sequence/3D similarity search; metadata via GraphQL; download PDB/mmCIF. For AlphaFold predictions use alphafold-database-access."
+ description: "Query RCSB PDB (200K+ structures) via the public REST + GraphQL APIs with plain `requests` (no SDK). Search by text, attribute, sequence, or 3D structure similarity (Search API); retrieve metadata via GraphQL (Data API); download PDB/mmCIF from files.rcsb.org. For AlphaFold predictions use alphafold-database-access; for protein sequences only use uniprot-protein-database."
license: "BSD-3-Clause"
---
# PDB Database
+ > **Why no SDK?** The `rcsb-api` Python SDK is convenient sugar over three public, no-auth REST endpoints (`search.rcsb.org`, `data.rcsb.org`, `files.rcsb.org`). When the SDK is unavailable, every operation can be reproduced with plain `requests` and a small JSON payload. This SKILL.md uses the REST path throughout so the code runs in any environment with `requests` installed.
+
## Overview
- RCSB PDB is the worldwide repository for 3D structural data of biological macromolecules with 200,000+ experimentally determined structures. The `rcsb-api` Python SDK provides unified access to Search API (find PDB IDs by text, attributes, sequence, or structure similarity) and Data API (retrieve metadata and coordinates). Use this skill for programmatic structural biology queries, drug target analysis, and protein family comparisons.
+ RCSB PDB is the worldwide repository for 3D structural data of biological macromolecules with 200,000+ experimentally determined structures. Programmatic access is via three free, no-auth endpoints:
+ | API | Base URL | Method | Purpose |
+ |---|---|---|---|
+ | **Search** | `https://search.rcsb.org/rcsbsearch/v2/query` | `POST` JSON | Find PDB IDs by text, attribute filters, sequence, or 3D similarity |
+ | **Data** | `https://data.rcsb.org/graphql` | `POST` GraphQL | Retrieve structured metadata (entries, polymer entities, assemblies, ligands) |
+ | **Files** | `https://files.rcsb.org/download/{id}.{format}` | `GET` | Download coordinate files (mmCIF, PDB, FASTA) |
+
+ Use this skill for programmatic structural biology queries, drug target analysis, and protein family comparisons.
+
## When to Use
- Searching for protein or nucleic acid crystal/cryo-EM/NMR structures by keyword or property
- Finding structures similar to a query sequence (MMseqs2) or 3D geometry (BioZernike)
- Retrieving experimental metadata (resolution, method, organism, deposition date) for structure sets
- Downloading coordinate files (PDB, mmCIF) for molecular dynamics, docking, or visualization
- Building structure-based datasets for machine learning or drug discovery pipelines
- Comparing protein-ligand complexes across a target family
- For AlphaFold predicted structures, use `alphafold-database-access` instead
- For protein sequence/annotation queries without structures, use `uniprot-protein-database` instead
## Prerequisites
- - **Python packages**: `rcsb-api` (unified Search + Data SDK), optionally `requests` for file downloads, `biopython` for coordinate parsing
- - **No API key required**: RCSB PDB is freely accessible
- - **Rate limits**: No published hard limit; implement exponential backoff on HTTP 429. Recommended: 2-5 requests/second for batch operations
+ - **Python packages**: `requests` (only requirement). Optional: `biopython` for parsing downloaded coordinate files.
+ - **No API key required**: RCSB PDB is freely accessible.
+ - **Rate limits**: No published hard limit. Polite delays of `time.sleep(0.2-0.5)` between requests are sufficient; implement exponential backoff on HTTP 429.
```bash
- pip install rcsb-api requests
- # Optional: pip install biopython # for PDB/mmCIF coordinate parsing
+ pip install requests
+ # Optional, for coordinate parsing:
+ pip install biopython
```
## Quick Start
- Typical search-then-fetch pattern:
+ Typical search-then-fetch pattern: hit the Search API, get a list of PDB IDs, then resolve metadata via the GraphQL Data API.
```python
- from rcsbapi.search import TextQuery, AttributeQuery
- from rcsbapi.search.attrs import rcsb_entry_info, rcsb_entity_source_organism
- from rcsbapi.data import fetch, Schema
+ import requests
- # 1. Search: high-resolution human kinases
- query = (
- TextQuery("kinase") &
- AttributeQuery(
- attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Homo sapiens"
- ) &
- AttributeQuery(
- attribute=rcsb_entry_info.resolution_combined,
- operator="less", value=2.0
- )
- )
- pdb_ids = list(query())
- print(f"Found {len(pdb_ids)} structures") # e.g., Found 1523 structures
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
+ DATA = "https://data.rcsb.org/graphql"
- # 2. Fetch metadata for first hit
- data = fetch(pdb_ids[0], schema=Schema.ENTRY)
- print(data["struct"]["title"])
- print(f"Resolution: {data['rcsb_entry_info']['resolution_combined']} A")
+ # 1. Search: human X-ray structures of "kinase" at resolution < 2.0 Å
+ payload = {
+ "query": {
+ "type": "group", "logical_operator": "and",
+ "nodes": [
+ {"type": "terminal", "service": "full_text",
+ "parameters": {"value": "kinase"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
+ "operator": "exact_match", "value": "Homo sapiens"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entry_info.resolution_combined",
+ "operator": "less", "value": 2.0}},
+ ],
+ },
+ "return_type": "entry",
+ "request_options": {"paginate": {"rows": 10}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ result = r.json()
+ pdb_ids = [hit["identifier"] for hit in result["result_set"]]
+ print(f"Total matches: {result['total_count']}, first batch: {pdb_ids}")
+
+ # 2. Fetch metadata for the first hit via GraphQL
+ gql = """{ entry(entry_id: "%s") {
+ struct { title }
+ exptl { method }
+ rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count }
+ } }""" % pdb_ids[0]
+ r2 = requests.post(DATA, json={"query": gql}, timeout=30)
+ entry = r2.json()["data"]["entry"]
+ print(entry["struct"]["title"])
+ print(f"Method: {entry['exptl'][0]['method']}, Resolution: {entry['rcsb_entry_info']['resolution_combined']} Å")
```
## Core API
### Module 1: Text and Attribute Search
- **Text search** finds entries by keyword across all indexed fields.
+ **Free-text search** uses `service: "full_text"` and searches across all indexed fields.
```python
- from rcsbapi.search import TextQuery
+ import requests
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
- query = TextQuery("hemoglobin")
- results = list(query())
- print(f"Found {len(results)} structures") # Found ~750 structures
+ def text_search(keyword, rows=25):
+ payload = {
+ "query": {"type": "terminal", "service": "full_text",
+ "parameters": {"value": keyword}},
+ "return_type": "entry",
+ "request_options": {"paginate": {"rows": rows}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ data = r.json()
+ return [hit["identifier"] for hit in data["result_set"]], data["total_count"]
+
+ ids, total = text_search("hemoglobin")
+ print(f"Found {total} structures; first batch: {ids[:5]}")
```
- **Attribute search** queries specific properties with typed operators.
+ **Attribute search** uses `service: "text"` with structured `attribute`/`operator`/`value` parameters.
```python
- from rcsbapi.search import AttributeQuery
- from rcsbapi.search.attrs import rcsb_entity_source_organism, exptl, rcsb_entry_info
+ import requests
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
+ def attribute_search(attribute, operator, value, return_type="entry", rows=25):
+ payload = {
+ "query": {"type": "terminal", "service": "text",
+ "parameters": {"attribute": attribute,
+ "operator": operator,
+ "value": value}},
+ "return_type": return_type,
+ "request_options": {"paginate": {"rows": rows}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ return r.json()
+
# Human proteins
- q_human = AttributeQuery(
- attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Homo sapiens"
- )
+ human = attribute_search("rcsb_entity_source_organism.scientific_name",
+ "exact_match", "Homo sapiens", rows=5)
+ print(f"Human structures: {human['total_count']}")
# X-ray only
- q_xray = AttributeQuery(
- attribute=exptl.method,
- operator="exact_match", value="X-RAY DIFFRACTION"
- )
+ xray = attribute_search("exptl.method", "exact_match", "X-RAY DIFFRACTION", rows=5)
+ print(f"X-ray structures: {xray['total_count']}")
- # Resolution range
- q_res = AttributeQuery(
- attribute=rcsb_entry_info.resolution_combined,
- operator="range", value=(1.5, 2.5)
+ # Resolution range: 1.5–2.5 Å
+ res = attribute_search(
+ "rcsb_entry_info.resolution_combined", "range",
+ {"from": 1.5, "to": 2.5, "include_lower": True, "include_upper": True},
+ rows=5
)
-
- results = list(q_human & q_xray & q_res)
- print(f"Human X-ray 1.5-2.5A: {len(results)} structures")
+ print(f"1.5–2.5 Å: {res['total_count']}")
```
### Module 2: Sequence Similarity Search
- Find structures with similar sequences using MMseqs2. Supports protein, DNA, and RNA.
+ Find structures with similar sequences using MMseqs2. Service is `"sequence"`; `target` selects protein vs. nucleic acid.
```python
- from rcsbapi.search import SequenceQuery
+ import requests
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
- # Protein sequence search (KRAS)
- query = SequenceQuery(
- value="MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAG"
- "QEEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDL"
- "PSRTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK",
- evalue_cutoff=0.1,
- identity_cutoff=0.9
- )
- results = list(query())
- print(f"Sequence hits: {len(results)}")
+ kras_seq = ("MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ"
+ "EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS"
+ "RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK")
- # DNA sequence search
- dna_query = SequenceQuery(
- value="ACGTACGTACGTACGTACGT",
- evalue_cutoff=1e-5,
- identity_cutoff=0.8,
- sequence_type="dna" # "protein" (default), "dna", "rna"
- )
+ payload = {
+ "query": {
+ "type": "terminal", "service": "sequence",
+ "parameters": {
+ "target": "pdb_protein_sequence", # or "pdb_dna_sequence", "pdb_rna_sequence"
+ "value": kras_seq,
+ "evalue_cutoff": 0.1,
+ "identity_cutoff": 0.9,
+ },
+ },
+ "return_type": "polymer_entity",
+ "request_options": {"paginate": {"rows": 10}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ data = r.json()
+ print(f"KRAS-like hits: {data['total_count']}")
+ for hit in data["result_set"][:5]:
+ print(f" {hit['identifier']} score={hit.get('score', 'n/a')}")
```
### Module 3: Structure Similarity Search
- Find structures with similar 3D geometry using BioZernike descriptors.
+ Find structures with similar 3D geometry using BioZernike descriptors. Service is `"structure"`; pass the reference entry + assembly ID.
```python
- from rcsbapi.search import StructSimilarityQuery
-
- # Search by full entry
- query = StructSimilarityQuery(
- structure_search_type="entry",
- entry_id="4HHB"
- )
- results = list(query())
- print(f"Structurally similar to 4HHB: {len(results)}")
-
- # Search by specific chain
- chain_query = StructSimilarityQuery(
- structure_search_type="chain",
- entry_id="4HHB",
- chain_id="A"
- )
+ import requests
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
- # Search by biological assembly
- assembly_query = StructSimilarityQuery(
- structure_search_type="assembly",
- entry_id="4HHB",
- assembly_id="1"
- )
+ payload = {
+ "query": {
+ "type": "terminal", "service": "structure",
+ "parameters": {
+ "value": {"entry_id": "4HHB", "assembly_id": "1"},
+ "operator": "strict_shape_match", # or "relaxed_shape_match"
+ },
+ },
+ "return_type": "polymer_entity",
+ "request_options": {"paginate": {"rows": 10}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ data = r.json()
+ print(f"Structurally similar to 4HHB: {data['total_count']}")
+ for hit in data["result_set"][:5]:
+ print(f" {hit['identifier']} score={hit.get('score', 'n/a')}")
```
- ### Module 4: Data Retrieval (Schema and GraphQL)
+ ### Module 4: Data Retrieval (GraphQL)
- Fetch structured metadata for known PDB IDs using Schema objects or custom GraphQL.
+ The GraphQL endpoint at `data.rcsb.org/graphql` is the canonical way to retrieve structured metadata for known PDB IDs. One request can pull fields across the full data hierarchy (entry → polymer_entity → assembly → chem_comp).
```python
- from rcsbapi.data import fetch, Schema
-
- # Entry-level data
- entry = fetch("4HHB", schema=Schema.ENTRY)
- print(entry["struct"]["title"])
- print(entry["exptl"][0]["method"]) # X-RAY DIFFRACTION
- print(entry["rcsb_entry_info"]["resolution_combined"]) # 1.74
+ import requests
+ DATA = "https://data.rcsb.org/graphql"
- # Polymer entity data (append _N for entity number)
- entity = fetch("4HHB_1", schema=Schema.POLYMER_ENTITY)
- print(entity["entity_poly"]["pdbx_seq_one_letter_code"][:50])
- print(entity["rcsb_entity_source_organism"][0]["scientific_name"])
+ # Entry-level metadata
+ gql = """{ entry(entry_id: "4HHB") {
+ struct { title }
+ exptl { method }
+ rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count nonpolymer_entity_count }
+ rcsb_accession_info { deposit_date initial_release_date }
+ } }"""
+ r = requests.post(DATA, json={"query": gql}, timeout=30)
+ entry = r.json()["data"]["entry"]
+ print(f"Title : {entry['struct']['title']}")
+ print(f"Method : {entry['exptl'][0]['method']}")
+ print(f"Resolution : {entry['rcsb_entry_info']['resolution_combined']} Å")
+ print(f"Atoms : {entry['rcsb_entry_info']['deposited_atom_count']}")
```
- **Custom GraphQL** for flexible cross-level queries in a single request:
-
```python
- from rcsbapi.data import fetch
+ # Polymer entity (sequence, organism, MW)
+ gql = """{ polymer_entity(entry_id: "4HHB", entity_id: "1") {
+ entity_poly { pdbx_seq_one_letter_code }
+ rcsb_polymer_entity { formula_weight }
+ rcsb_entity_source_organism { scientific_name ncbi_taxonomy_id }
+ } }"""
+ r = requests.post(DATA, json={"query": gql}, timeout=30)
+ pe = r.json()["data"]["polymer_entity"]
+ print(f"Sequence (first 50): {pe['entity_poly']['pdbx_seq_one_letter_code'][:50]}")
+ print(f"Organism : {pe['rcsb_entity_source_organism'][0]['scientific_name']}")
+ print(f"MW : {pe['rcsb_polymer_entity']['formula_weight']}")
+ ```
- gql = """{
- entry(entry_id: "4HHB") {
- struct { title }
- exptl { method }
- rcsb_entry_info { resolution_combined deposited_atom_count polymer_entity_count }
- rcsb_accession_info { deposit_date initial_release_date }
- }
- }"""
- data = fetch(query_type="graphql", query=gql)
- info = data["data"]["entry"]
- print(f"Title: {info['struct']['title']}")
- print(f"Atoms: {info['rcsb_entry_info']['deposited_atom_count']}")
+ ```python
+ # Batch: pull metadata for many entries in one request
+ gql = """{ entries(entry_ids: ["4HHB", "1A3N", "1HHB"]) {
+ rcsb_id
+ struct { title }
+ exptl { method }
+ rcsb_entry_info { resolution_combined }
+ } }"""
+ r = requests.post(DATA, json={"query": gql}, timeout=30)
+ for e in r.json()["data"]["entries"]:
+ res = e["rcsb_entry_info"]["resolution_combined"]
+ print(f" {e['rcsb_id']}: {e['exptl'][0]['method']:<25} {res} Å — {e['struct']['title'][:40]}")
```
### Module 5: File Download
- Download coordinate files from RCSB file servers.
+ Coordinate files (mmCIF, PDB, FASTA, assembly variants) are served directly from `files.rcsb.org`.
```python
import requests
def download_structure(pdb_id, fmt="cif", output_dir="."):
- """Download PDB/mmCIF/FASTA. URLs: .pdb, .cif, /fasta/entry/{ID}, .pdb1 (assembly)."""
+ """Download mmCIF / PDB / FASTA. URLs: .pdb, .cif, /fasta/entry/{ID}, .pdb1 (assembly)."""
url = f"https://files.rcsb.org/download/{pdb_id}.{fmt}"
- resp = requests.get(url)
- if resp.status_code == 200:
+ r = requests.get(url, timeout=60)
+ if r.status_code == 200:
path = f"{output_dir}/{pdb_id}.{fmt}"
+ # mmCIF / PDB are text; assemblies and biological units are also text
with open(path, "w") as f:
- f.write(resp.text)
- print(f"Downloaded {path} ({len(resp.text)} bytes)")
+ f.write(r.text)
+ print(f"Downloaded {path} ({len(r.text)/1024:.1f} KB)")
return path
- print(f"Error {resp.status_code} for {pdb_id}")
+ print(f"HTTP {r.status_code} for {pdb_id}.{fmt}")
return None
- # Download both formats
- download_structure("4HHB", fmt="pdb")
download_structure("4HHB", fmt="cif")
+ download_structure("4HHB", fmt="pdb")
```
- ### Module 6: Query Composition
+ ```python
+ # FASTA sequence for an entry
+ r = requests.get("https://www.rcsb.org/fasta/entry/4HHB", timeout=30)
+ r.raise_for_status()
+ print(r.text[:400])
+ ```
- Combine queries with Python bitwise operators (`&` AND, `|` OR, `~` NOT).
+ ### Module 6: Query Composition (group + logical_operator)
+ Combine terminal queries with `type: "group"` and a `logical_operator` of `"and"` / `"or"`. Nested groups give arbitrary boolean expressions; negation is via `"node_id"` references with `"operator": "negate"` on the group (rare — usually expressed as the inverse attribute filter).
+
```python
- from rcsbapi.search import TextQuery, AttributeQuery
- from rcsbapi.search.attrs import rcsb_entry_info, rcsb_entity_source_organism, refine
- import datetime
+ import requests, datetime
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
# AND: high-resolution human structures
- q_and = (
- AttributeQuery(attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Homo sapiens") &
- AttributeQuery(attribute=rcsb_entry_info.resolution_combined,
- operator="less", value=2.0)
- )
+ q_and = {
+ "type": "group", "logical_operator": "and",
+ "nodes": [
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
+ "operator": "exact_match", "value": "Homo sapiens"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entry_info.resolution_combined",
+ "operator": "less", "value": 2.0}},
+ ],
+ }
# OR: human or mouse
- q_or = (
- AttributeQuery(attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Homo sapiens") |
- AttributeQuery(attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Mus musculus")
- )
-
- # NOT: exclude low resolution
- q_not = TextQuery("protein") & ~AttributeQuery(
- attribute=rcsb_entry_info.resolution_combined, operator="greater", value=3.0
- )
+ q_or = {
+ "type": "group", "logical_operator": "or",
+ "nodes": [
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
+ "operator": "exact_match", "value": "Homo sapiens"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
+ "operator": "exact_match", "value": "Mus musculus"}},
+ ],
+ }
- # Complex: recent high-quality structures
+ # Combined: recent (last 30 days) + high-quality
one_month_ago = (datetime.date.today() - datetime.timedelta(days=30)).isoformat()
- from rcsbapi.search.attrs import rcsb_accession_info
- q_complex = (
- AttributeQuery(attribute=rcsb_entry_info.resolution_combined,
- operator="less", value=2.0) &
- AttributeQuery(attribute=refine.ls_R_factor_R_free,
- operator="less", value=0.25) &
- AttributeQuery(attribute=rcsb_accession_info.initial_release_date,
- operator="range", value=(one_month_ago, datetime.date.today().isoformat()))
- )
- print(f"Recent high-quality: {len(list(q_complex()))} structures")
+ today = datetime.date.today().isoformat()
+ q_recent_hq = {
+ "type": "group", "logical_operator": "and",
+ "nodes": [
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entry_info.resolution_combined",
+ "operator": "less", "value": 2.0}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "refine.ls_R_factor_R_free",
+ "operator": "less", "value": 0.25}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_accession_info.initial_release_date",
+ "operator": "range",
+ "value": {"from": one_month_ago, "to": today,
+ "include_lower": True, "include_upper": True}}},
+ ],
+ }
+
+ payload = {"query": q_recent_hq, "return_type": "entry",
+ "request_options": {"paginate": {"rows": 5}}}
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ print(f"Recent high-quality: {r.json()['total_count']} structures")
```
- ### Module 7: Batch Operations with Rate Limiting
+ ### Module 7: Pagination + Batch with Rate Limiting
- Fetch data for multiple structures with exponential backoff.
+ Search responses include `total_count`. Paginate with `request_options.paginate.start` and `rows` (max ~10000 per page in practice; 100–500 is a good batch size).
```python
- import time
- from rcsbapi.search import TextQuery
- from rcsbapi.data import fetch, Schema
+ import requests, time
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
- def batch_fetch(pdb_ids, schema=Schema.ENTRY, delay=0.5, max_retries=3):
- """Fetch metadata for multiple PDB IDs with rate-limit handling."""
- results = {}
- for i, pdb_id in enumerate(pdb_ids):
- wait = delay
- for attempt in range(max_retries):
- try:
- results[pdb_id] = fetch(pdb_id, schema=schema)
- break
- except Exception as e:
- if "429" in str(e):
- print(f"Rate limited on {pdb_id}, waiting {wait}s...")
- time.sleep(wait)
- wait *= 2
- else:
- print(f"Error {pdb_id}: {e}")
- break
- if (i + 1) % 10 == 0:
- print(f"Fetched {i + 1}/{len(pdb_ids)}")
+ def search_all(query_node, return_type="entry", page=100, max_results=None, delay=0.3):
+ """Paginate through every result; rate-limit between pages."""
+ out, start = [], 0
+ while True:
+ payload = {"query": query_node, "return_type": return_type,
+ "request_options": {"paginate": {"start": start, "rows": page}}}
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ d = r.json()
+ batch = [h["identifier"] for h in d.get("result_set", [])]
+ if not batch:
+ break
+ out.extend(batch)
+ if max_results and len(out) >= max_results:
+ return out[:max_results]
+ if len(batch) < page or len(out) >= d.get("total_count", 0):
+ break
+ start += page
time.sleep(delay)
- return results
+ return out
- # Usage
- query = TextQuery("insulin")
- pdb_ids = list(query())[:20] # First 20 hits
- data = batch_fetch(pdb_ids, delay=0.3)
- for pid, d in list(data.items())[:3]:
- print(f"{pid}: {d['struct']['title']}")
+ # Example: every insulin entry
+ ids = search_all(
+ {"type": "terminal", "service": "full_text", "parameters": {"value": "insulin"}},
+ max_results=300,
+ )
+ print(f"Insulin entries collected: {len(ids)}")
```
- ## Key Concepts
-
- ### PDB Data Hierarchy
-
- PDB organizes data in a strict hierarchy. Use the correct Schema for each level:
-
- | Level | ID Format | Schema | Content |
- |-------|-----------|--------|---------|
- | Entry | `4HHB` | `Schema.ENTRY` | Experiment, resolution, deposition |
- | Polymer Entity | `4HHB_1` | `Schema.POLYMER_ENTITY` | Sequence, organism, molecular weight |
- | Nonpolymer Entity | `4HHB_1` | `Schema.NONPOLYMER_ENTITY` | Ligands, cofactors, ions |
- | Branched Entity | `4HHB_1` | `Schema.BRANCHED_ENTITY` | Oligosaccharides |
- | Assembly | `4HHB/1` | `Schema.ASSEMBLY` | Biological unit, stoichiometry |
- | Chain Instance | `4HHB.A` | `Schema.POLYMER_ENTITY_INSTANCE` | Individual chain coordinates |
- | Chemical Component | `ATP` | `Schema.CHEM_COMP` | Small molecule metadata |
+ ```python
+ # Batch metadata fetch via GraphQL `entries(...)` to avoid one round-trip per ID
+ DATA = "https://data.rcsb.org/graphql"
- ### Common Data Fields
+ def batch_metadata(pdb_ids, chunk=50):
+ """Fetch (title, method, resolution) for many entries with one POST per chunk."""
+ all_rows = []
+ for i in range(0, len(pdb_ids), chunk):
+ ids_arr = pdb_ids[i:i+chunk]
+ ids_str = ", ".join(f'"{p}"' for p in ids_arr)
+ gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
+ rcsb_id
+ struct {{ title }}
+ exptl {{ method }}
+ rcsb_entry_info {{ resolution_combined }}
+ }} }}"""
+ r = requests.post(DATA, json={"query": gql}, timeout=60)
+ r.raise_for_status()
+ for e in r.json()["data"]["entries"]:
+ res = e["rcsb_entry_info"]["resolution_combined"]
+ all_rows.append({
+ "pdb_id": e["rcsb_id"],
+ "method": e["exptl"][0]["method"] if e["exptl"] else None,
+ "resolution": res[0] if isinstance(res, list) and res else res,
+ "title": e["struct"]["title"],
+ })
+ return all_rows
- **Entry**: `struct.title`, `exptl[].method`, `rcsb_entry_info.resolution_combined`, `rcsb_entry_info.deposited_atom_count`, `rcsb_accession_info.deposit_date`, `rcsb_accession_info.initial_release_date`
+ rows = batch_metadata(ids[:20])
+ for r in rows[:5]:
+ print(f" {r['pdb_id']}: {r['method']:<25} {r['resolution']} Å — {r['title'][:50]}")
+ ```
- **Polymer entity**: `entity_poly.pdbx_seq_one_letter_code`, `rcsb_polymer_entity.formula_weight`, `rcsb_entity_source_organism.scientific_name`, `rcsb_entity_source_organism.ncbi_taxonomy_id`
+ ## Key Concepts
- **Assembly**: `rcsb_assembly_info.polymer_entity_count`, `rcsb_assembly_info.assembly_id`
+ ### Search Service Cheat Sheet
- ### Search Query Types
+ | Service | Use case | Required parameters |
+ |---|---|---|
+ | `full_text` | Free-text keyword across all indexed fields | `value` (string) |
+ | `text` | Structured attribute filter | `attribute`, `operator`, `value` |
+ | `sequence` | MMseqs2 sequence similarity | `target` ∈ {`pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence`}, `value` (sequence), `evalue_cutoff`, `identity_cutoff` |
+ | `seqmotif` | Pattern / regex / PROSITE motif | `value` (pattern), `pattern_type` ∈ {`simple`, `prosite`, `regex`} |
+ | `structure` | 3D shape similarity (BioZernike) | `value` (`{entry_id, assembly_id}`), `operator` ∈ {`strict_shape_match`, `relaxed_shape_match`} |
+ | `strucmotif` | 3D residue-arrangement motif | `value` (residue list), `rmsd_cutoff` |
+ | `chemical` | Ligand similarity by SMILES/InChI | `value`, `match_type` ∈ {`graph-exact`, `graph-relaxed`, `fingerprint-similarity`, `sub-structure-stereo-relaxed`} |
- | Query Type | Class | Use Case | Engine |
- |------------|-------|----------|--------|
- | Full text | `TextQuery` | Keyword search across all fields | Lucene |
- | Attribute | `AttributeQuery` | Property filters (organism, resolution) | Exact match |
- | Sequence | `SequenceQuery` | Sequence similarity (protein/DNA/RNA) | MMseqs2 |
- | Sequence Motif | `SequenceMotifQuery` | Regex/PROSITE motif patterns | Custom |
- | Structure | `StructSimilarityQuery` | 3D geometry similarity | BioZernike |
- | Structure Motif | `StructMotifQuery` | 3D residue arrangement patterns | InvertedIndex |
- | Chemical | `ChemSimilarityQuery` | Ligand similarity by SMILES/InChI | Fingerprint |
+ ### AttributeQuery Operators (service: `"text"`)
- ### AttributeQuery Operators
+ | Operator | Value shape | Example |
+ |---|---|---|
+ | `exact_match` | string | `"Homo sapiens"` |
+ | `contains_words` / `contains_phrase` | string | `"tyrosine kinase"` |
+ | `equals` / `greater` / `less` / `greater_or_equal` / `less_or_equal` | number | `2.0` |
+ | `range` | `{from, to, include_lower, include_upper}` | `{"from": 1.5, "to": 2.5, "include_lower": True, "include_upper": True}` |
+ | `exists` | (none) | — |
+ | `in` | array | `["X-RAY DIFFRACTION", "ELECTRON MICROSCOPY"]` |
- | Operator | Type | Example Value |
- |----------|------|---------------|
- | `exact_match` | String | `"Homo sapiens"` |
- | `contains_words` | String | `"kinase inhibitor"` |
- | `contains_phrase` | String | `"tyrosine kinase"` |
- | `equals` | Numeric | `2.0` |
- | `greater` / `less` | Numeric | `3.0` |
- | `greater_or_equal` / `less_or_equal` | Numeric | `1.5` |
- | `range` | Numeric/Date | `(1.5, 2.5)` or `("2024-01-01", "2024-12-31")` |
- | `exists` | Any | `None` (field has value) |
- | `in` | List | `["X-RAY DIFFRACTION", "ELECTRON MICROSCOPY"]` |
+ ### Return Types
- ### File Format Comparison
+ `return_type` controls the granularity of identifiers in `result_set`:
- | Format | Extension | Best For | Notes |
- |--------|-----------|----------|-------|
- | PDB | `.pdb` | Legacy tools, small structures | 99,999 atom limit, fixed-width |
- | mmCIF | `.cif` | Modern standard, large structures | No atom limit, key-value pairs |
- | BinaryCIF | `.bcif` | Web viewers, bandwidth-limited | Compressed binary, via ModelServer |
+ | `return_type` | Identifier shape | Example |
+ |---|---|---|
+ | `entry` | `4HHB` | One per PDB ID |
+ | `polymer_entity` | `4HHB_1` | One per polymer chain entity |
+ | `non_polymer_entity` | `4HHB_2` | Ligands, cofactors |
+ | `assembly` | `4HHB-1` | Biological unit |
+ | `polymer_instance` | `4HHB.A` | Individual chain coordinates |
+ | `mol_definition` | `HEM` | Chemical component (PDB ligand code) |
- ### Return Types
+ ### Common Data API GraphQL Roots
- Control what search results return via `ReturnType`:
+ | Root | Identifier shape | Returns |
+ |---|---|---|
+ | `entry(entry_id: ...)` | `"4HHB"` | Entry-level metadata |
+ | `entries(entry_ids: [...])` | array | Batch entry lookup |
+ | `polymer_entity(entry_id: ..., entity_id: ...)` | `"4HHB"`, `"1"` | Sequence + organism |
+ | `polymer_entity_instance(entry_id: ..., asym_id: ...)` | `"4HHB"`, `"A"` | Chain-level coords/metadata |
+ | `assembly(entry_id: ..., assembly_id: ...)` | `"4HHB"`, `"1"` | Biological assembly |
+ | `chem_comp(comp_id: ...)` | `"HEM"` | Small molecule reference |
- ```python
- from rcsbapi.search import TextQuery, ReturnType
+ ### File Formats
- query = TextQuery("hemoglobin")
- # Default: PDB IDs
- ids = list(query()) # ['4HHB', '1A3N', ...]
- # With scores
- scored = list(query(return_type=ReturnType.ENTRY, return_scores=True))
- # [{'identifier': '4HHB', 'score': 0.95}, ...]
- # Polymer entities
- entities = list(query(return_type=ReturnType.POLYMER_ENTITY))
- # ['4HHB_1', '4HHB_2', ...]
- ```
+ | Format | URL pattern | Notes |
+ |---|---|---|
+ | mmCIF | `https://files.rcsb.org/download/{id}.cif` | Recommended; no atom-count limit |
+ | PDB | `https://files.rcsb.org/download/{id}.pdb` | Legacy; 99,999 atom limit |
+ | Assembly (mmCIF) | `https://files.rcsb.org/download/{id}-assembly{N}.cif` | Biological unit |
+ | FASTA | `https://www.rcsb.org/fasta/entry/{id}` | Sequence only |
## Common Workflows
- ### Workflow 1: Drug Target Structure Search
+ ### Workflow 1: Drug Target Structure Set
- **Goal**: Find high-resolution structures of a drug target with bound ligands.
+ **Goal**: Find high-resolution human EGFR structures with bound ligands.
```python
- from rcsbapi.search import TextQuery, AttributeQuery
- from rcsbapi.search.attrs import rcsb_entry_info, rcsb_entity_source_organism
- from rcsbapi.data import fetch, Schema
- import time
+ import requests, time
- # Search: human EGFR, high resolution
- query = (
- TextQuery("EGFR epidermal growth factor receptor") &
- AttributeQuery(attribute=rcsb_entity_source_organism.scientific_name,
- operator="exact_match", value="Homo sapiens") &
- AttributeQuery(attribute=rcsb_entry_info.resolution_combined,
- operator="less", value=2.5)
- )
- pdb_ids = list(query())
- print(f"EGFR structures: {len(pdb_ids)}")
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
+ DATA = "https://data.rcsb.org/graphql"
- # Filter for structures with bound ligands
- targets = []
- for pid in pdb_ids[:10]:
- data = fetch(pid, schema=Schema.ENTRY)
- n_lig = data.get("rcsb_entry_info", {}).get("nonpolymer_entity_count", 0)
- if n_lig and n_lig > 0:
- targets.append({"pdb_id": pid, "title": data["struct"]["title"],
- "resolution": data["rcsb_entry_info"]["resolution_combined"],
- "ligands": n_lig})
- time.sleep(0.3)
+ payload = {
+ "query": {
+ "type": "group", "logical_operator": "and",
+ "nodes": [
+ {"type": "terminal", "service": "full_text",
+ "parameters": {"value": "EGFR epidermal growth factor receptor"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entity_source_organism.scientific_name",
+ "operator": "exact_match", "value": "Homo sapiens"}},
+ {"type": "terminal", "service": "text",
+ "parameters": {"attribute": "rcsb_entry_info.resolution_combined",
+ "operator": "less", "value": 2.5}},
+ ],
+ },
+ "return_type": "entry",
+ "request_options": {"paginate": {"rows": 50}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ r.raise_for_status()
+ pdb_ids = [h["identifier"] for h in r.json()["result_set"]]
+ print(f"EGFR ≤2.5 Å human structures: {len(pdb_ids)}")
- for t in sorted(targets, key=lambda x: x["resolution"]):
- print(f"{t['pdb_id']}: {t['resolution']}A, {t['ligands']} ligands - {t['title'][:60]}")
+ # Filter to entries with bound ligands via batch GraphQL
+ ids_str = ", ".join(f'"{p}"' for p in pdb_ids[:20])
+ gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
+ rcsb_id
+ struct {{ title }}
+ rcsb_entry_info {{ resolution_combined nonpolymer_entity_count }}
+ }} }}"""
+ r2 = requests.post(DATA, json={"query": gql}, timeout=60)
+ for e in r2.json()["data"]["entries"]:
+ n_lig = e["rcsb_entry_info"]["nonpolymer_entity_count"] or 0
+ if n_lig > 0:
+ res = e["rcsb_entry_info"]["resolution_combined"]
+ res_v = res[0] if isinstance(res, list) else res
+ print(f" {e['rcsb_id']}: {res_v} Å, ligands={n_lig} — {e['struct']['title'][:60]}")
+ time.sleep(0.05)
```
- ### Workflow 2: Protein Family Structural Comparison
+ ### Workflow 2: Protein Family — Sequence-Similar Structures
- **Goal**: Compare structures across a protein family by sequence similarity.
+ **Goal**: Find all PDB structures with sequence similar to a query (KRAS), then summarize their resolution + experimental method.
```python
- from rcsbapi.search import SequenceQuery
- from rcsbapi.data import fetch, Schema
- import time
+ import requests, time
- # Find structures similar to KRAS
- query = SequenceQuery(
- value="MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAG",
- evalue_cutoff=1e-5, identity_cutoff=0.5
- )
- hits = list(query())
- print(f"Family members: {len(hits)}")
+ SEARCH = "https://search.rcsb.org/rcsbsearch/v2/query"
+ DATA = "https://data.rcsb.org/graphql"
- # Collect metadata with rate limiting
- family = []
- for pid in hits[:15]:
- try:
- d = fetch(pid, schema=Schema.ENTRY)
- family.append({"pdb_id": pid, "title": d["struct"]["title"],
- "resolution": d.get("rcsb_entry_info", {}).get("resolution_combined"),
- "method": d["exptl"][0]["method"]})
- except Exception as e:
- print(f"Skip {pid}: {e}")
- time.sleep(0.3)
+ kras_seq = ("MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQVVIDGETCLLDILDTAGQ"
+ "EEYSAMRDQYMRTGEGFLCVFAINNTKSFEDIHHYREQIKRVKDSEDVPMVLVGNKCDLPS"
+ "RTVDTKQAQDLARSYGIPFIETSAKTRQGVDDAFYTLVREIRKHKEKMSK")
- for f in sorted(family, key=lambda x: x["resolution"] or 99):
- res = f"{f['resolution']:.1f}" if f["resolution"] else "N/A"
- print(f"{f['pdb_id']}: {res}A {f['method']:<20} {f['title'][:50]}")
+ payload = {
+ "query": {
+ "type": "terminal", "service": "sequence",
+ "parameters": {"target": "pdb_protein_sequence", "value": kras_seq,
+ "evalue_cutoff": 1e-5, "identity_cutoff": 0.5},
+ },
+ "return_type": "polymer_entity",
+ "request_options": {"paginate": {"rows": 20}},
+ }
+ r = requests.post(SEARCH, json=payload, timeout=30)
+ hits = r.json()["result_set"]
+ print(f"KRAS family hits: {len(hits)}")
+
+ # Unique PDB IDs from polymer_entity identifiers (e.g., "4OBE_1" -> "4OBE")
+ entry_ids = sorted({h["identifier"].split("_")[0] for h in hits})
+
+ # Batch metadata
+ ids_str = ", ".join(f'"{p}"' for p in entry_ids)
+ gql = f"""{{ entries(entry_ids: [{ids_str}]) {{
+ rcsb_id
+ struct {{ title }}
+ exptl {{ method }}
+ rcsb_entry_info {{ resolution_combined }}
+ }} }}"""
+ r2 = requests.post(DATA, json={"query": gql}, timeout=60)
+ for e in sorted(r2.json()["data"]["entries"],
+ key=lambda x: (x["rcsb_entry_info"]["resolution_combined"] or [99])[0] if isinstance(x["rcsb_entry_info"]["resolution_combined"], list) else (x["rcsb_entry_info"]["resolution_combined"] or 99)):
+ res = e["rcsb_entry_info"]["resolution_combined"]
+ res_v = res[0] if isinstance(res, list) else res
+ print(f" {e['rcsb_id']}: {res_v} Å {e['exptl'][0]['method']:<25} {e['struct']['title'][:50]}")
```
- ### Workflow 3: Structure Download and Coordinate Parsing
+ ### Workflow 3: Download + Parse with BioPython
- **Goal**: Download a structure and extract chain-level information with BioPython.
+ **Goal**: Download mmCIF, then enumerate chains with BioPython.
```python
import requests
- from Bio.PDB import PDBParser, MMCIFParser
+ from Bio.PDB import MMCIFParser
- # Download mmCIF
pdb_id = "4HHB"
- url = f"https://files.rcsb.org/download/{pdb_id}.cif"
- resp = requests.get(url)
+ r = requests.get(f"https://files.rcsb.org/download/{pdb_id}.cif", timeout=60)
+ r.raise_for_status()
with open(f"{pdb_id}.cif", "w") as f:
- f.write(resp.text)
+ f.write(r.text)
- # Parse and analyze
parser = MMCIFParser(QUIET=True)
structure = parser.get_structure(pdb_id, f"{pdb_id}.cif")
-
for model in structure:
for chain in model:
- residues = [r for r in chain if r.id[0] == " "] # standard residues
- atoms = sum(len(list(r.get_atoms())) for r in residues)
- print(f"Chain {chain.id}: {len(residues)} residues, {atoms} atoms")
- # Chain A: 141 residues, 1070 atoms
- # Chain B: 146 residues, 1123 atoms
- # ...
+ std_res = [r for r in chain if r.id[0] == " "]
+ atoms = sum(len(list(r.get_atoms())) for r in std_res)
+ print(f"Chain {chain.id}: {len(std_res)} residues, {atoms} atoms")
```
## Key Parameters
- | Parameter | Function/Endpoint | Default | Range / Options | Effect |
- |-----------|-------------------|---------|-----------------|--------|
- | `evalue_cutoff` | `SequenceQuery` | `0.1` | `1e-10` to `10` | E-value threshold for sequence hits |
- | `identity_cutoff` | `SequenceQuery` | `0.9` | `0.0`-`1.0` | Minimum sequence identity fraction |
- | `sequence_type` | `SequenceQuery` | `"protein"` | `"protein"`, `"dna"`, `"rna"` | Query sequence type |
- | `operator` | `AttributeQuery` | (required) | See Operators table | Comparison type for attribute filter |
- | `structure_search_type` | `StructSimilarityQuery` | (required) | `"entry"`, `"chain"`, `"assembly"` | Granularity of structure comparison |
- | `return_type` | `query()` | `ReturnType.ENTRY` | `ENTRY`, `POLYMER_ENTITY`, `ASSEMBLY`, etc. | What identifiers to return |
- | `schema` | `fetch()` | (required) | `Schema.ENTRY`, `POLYMER_ENTITY`, `ASSEMBLY`, etc. | Data hierarchy level to retrieve |
+ | Parameter | Endpoint | Default | Range / Options | Effect |
+ |-----------|----------|---------|-----------------|--------|
+ | `value` | search `sequence` | required | protein/DNA/RNA sequence string | Query sequence for MMseqs2 |
+ | `evalue_cutoff` | search `sequence` | `0.1` | `1e-10`–`10` | E-value threshold |
+ | `identity_cutoff` | search `sequence` | `0.9` | `0.0`–`1.0` | Minimum identity fraction |
+ | `target` | search `sequence` | `"pdb_protein_sequence"` | `pdb_protein_sequence`, `pdb_dna_sequence`, `pdb_rna_sequence` | Sequence type |
+ | `operator` | search `text` (attribute) | required | see Attribute Operators | Comparison kind |
+ | `operator` | search `structure` | `strict_shape_match` | `strict_shape_match`, `relaxed_shape_match` | 3D match stringency |
+ | `return_type` | all search | `entry` | `entry`, `polymer_entity`, `assembly`, `polymer_instance`, `mol_definition`, … | Identifier granularity |
+ | `paginate.start` / `paginate.rows` | `request_options` | `0` / `25` | up to ~10000 rows/page in practice | Pagination window |
+ | GraphQL field `entries(entry_ids: [...])` | `data.rcsb.org/graphql` | — | array of PDB IDs | Batch entry metadata |
## Best Practices
- 1. **Search then fetch**: Use Search API to get PDB IDs first, then Data API to retrieve metadata. Avoid fetching blindly.
+ 1. **Search → fetch**: Use the Search API to get a list of IDs, then GraphQL `entries(entry_ids: [...])` for batch metadata. Avoid one GraphQL request per ID.
- 2. **Use GraphQL for complex queries**: When you need fields from multiple hierarchy levels (entry + entity + assembly), a single GraphQL query is more efficient than multiple REST calls.
+ 2. **Use `full_text` vs `text` deliberately**: free-text keyword search needs `"service": "full_text"`. Structured attribute filters need `"service": "text"`. They are not interchangeable.
- 3. **Cache aggressively**: PDB data changes weekly (Wednesday releases). Cache results within a session to avoid redundant requests.
+ 3. **mmCIF over PDB format**: PDB format is being phased out and has a 99,999 atom limit. Always download `.cif` for new code.
- 4. **Prefer mmCIF over PDB format**: PDB format has a 99,999 atom limit and is being phased out. Always use `.cif` for new work.
+ 4. **Set realistic `paginate.rows`**: `rows: 100` is a good default for batch work; the API may slow down beyond ~10000. Loop with `paginate.start` for full traversal.
- 5. **Use typed attribute imports**: Import attributes from `rcsbapi.search.attrs` for IDE autocompletion and typo prevention:
- ```python
- from rcsbapi.search.attrs import rcsb_entry_info
- # Better than raw string: "rcsb_entry_info.resolution_combined"
- ```
+ 5. **Rate limit with `time.sleep(0.2)` in batch loops**: No published hard cap, but the public infrastructure is shared. On `HTTP 429`, back off exponentially.
- 6. **Inspect query JSON for debugging**: Call `query.to_dict()` to see the JSON payload sent to the API.
+ 6. **Inspect the payload before posting**: `print(json.dumps(payload, indent=2))` is the cheapest way to debug `HTTP 400` errors.
- ## Troubleshooting
+ 7. **`entries(entry_ids: [...])` does not validate every ID**: if one ID is wrong, the whole array returns `null` entries. Validate IDs separately if you can't trust the source.
- | Problem | Cause | Solution |
- |---------|-------|----------|
- | `ModuleNotFoundError: rcsbapi` | Package not installed | `pip install rcsb-api` (not `rcsbsearchapi`, which is deprecated) |
- | HTTP 404 on fetch | PDB ID obsolete or invalid | Check ID at rcsb.org; use `rcsb_accession_info.status_code` to find superseding entry |
- | HTTP 429 Too Many Requests | Rate limit exceeded | Add `time.sleep(0.5)` between requests; use exponential backoff (see Module 7) |
- | HTTP 500 Internal Server Error | Temporary server issue | Retry after 5-10s delay; check status.rcsb.org |
- | Empty search results | Query too restrictive or attribute name wrong | Relax filters; verify attribute paths via `rcsbapi.search.attrs` |
- | `KeyError` on fetch result | Field not present for this entry | Use `.get()` with default: `data.get("rcsb_entry_info", {}).get("resolution_combined")` |
- | Sequence search returns 0 hits | E-value or identity too strict | Lower `identity_cutoff` (try 0.3) and raise `evalue_cutoff` (try 1.0) |
- | Downloaded PDB file truncated | Structure too large for PDB format | Download mmCIF (`.cif`) instead; PDB has 99,999 atom limit |
+ ## Common Recipes
- ## Bundled Resources
+ ### Recipe: Get FASTA for a PDB ID
- **Self-contained consolidation**: Consolidates original SKILL.md (308 lines) and references/api_reference.md (618 lines, 926 total). ~150 lines of duplicated code deduplicated; effective original ~776 lines.
+ ```python
+ import requests
+ r = requests.get("https://www.rcsb.org/fasta/entry/4HHB", timeout=30)
+ print(r.text)
+ ```
- Content from api_reference.md consolidated into: Key Concepts (data hierarchy, fields, query types, operators, return types), Core API Modules 1-7 (search patterns, GraphQL, download helper, rate limiting, batch), Workflows (drug-target, quality filtering), Troubleshooting table.
+ ### Recipe: All chains in an entry
- **Omitted**: ModelServer API, VolumeServer API, Sequence Coordinates API, Alignment API (4 specialized server APIs -- rarely used programmatically; documented at data.rcsb.org). Debugging subprocess/curl tip replaced by `to_dict()` in Best Practices.
+ ```python
+ import requests
+ DATA = "https://data.rcsb.org/graphql"
+ gql = """{ entry(entry_id: "4HHB") {
+ polymer_entities {
+ rcsb_id
+ rcsb_polymer_entity_container_identifiers { auth_asym_ids }
+ entity_poly { rcsb_entity_polymer_type pdbx_seq_one_letter_code_can }
+ }
+ } }"""
+ r = requests.post(DATA, json={"query": gql}, timeout=30)
+ for pe in r.json()["data"]["entry"]["polymer_entities"]:
+ chains = pe["rcsb_polymer_entity_container_identifiers"]["auth_asym_ids"]
+ seq = pe["entity_poly"]["pdbx_seq_one_letter_code_can"][:50]
+ print(f" {pe['rcsb_id']} chains={chains} type={pe['entity_poly']['rcsb_entity_polymer_type']} seq={seq}…")
+ ```
+ ### Recipe: List all ligands in an entry
+
+ ```python
+ import requests
+ DATA = "https://data.rcsb.org/graphql"
+ gql = """{ entry(entry_id: "1IEP") {
+ nonpolymer_entities {
+ rcsb_id
+ nonpolymer_comp { chem_comp { id name formula } }
+ }
+ } }"""
+ r = requests.post(DATA, json={"query": gql}, timeout=30)
+ for npe in r.json()["data"]["entry"]["nonpolymer_entities"]:
+ cc = npe["nonpolymer_comp"]["chem_comp"]
+ print(f" {npe['rcsb_id']}: {cc['id']} ({cc['name']}) {cc['formula']}")
+ ```
+
+ ### Recipe: Inspect available search attributes
+
+ The Search API exposes a JSON schema at `https://search.rcsb.org/rcsbsearch/v2/metadata/schema`. Use it to look up valid attribute paths.
+
+ ```python
+ import requests
+ r = requests.get("https://search.rcsb.org/rcsbsearch/v2/metadata/schema", timeout=30)
+ schema = r.json()
+ # Schema lists hundreds of attribute paths; sample a few
+ sample_paths = [k for k in schema if "resolution" in k.lower()][:5]
+ print(sample_paths)
+ ```
+
+ ## Troubleshooting
+
+ | Problem | Cause | Solution |
+ |---------|-------|----------|
+ | `HTTP 400 — Invalid request to the [ text ] service` on a free-text query | Wrong service name | Use `"service": "full_text"` for keyword search; `"service": "text"` is for structured attribute filters |
+ | `HTTP 400` with cryptic schema message | Bad operator/value shape | Check the AttributeQuery Operators table; `range` needs the `{from,to,include_lower,include_upper}` dict |
+ | Empty `result_set` | Filters too strict | Relax filters one at a time; verify attribute names via the schema endpoint |
+ | `HTTP 404` on `entries(entry_ids: ["XYZW"])` | The entry doesn't exist | RCSB returns `null` rather than 404 inside the GraphQL response — check each `data.entries[i]` for null |
+ | `HTTP 429 Too Many Requests` | Burst pace | Add `time.sleep(0.3)` between requests; exponential backoff on 429 |
+ | `HTTP 500` from search | Server-side glitch | Retry after 5–10 s; check `status.rcsb.org` |
+ | Downloaded `.pdb` file truncated | >99,999 atoms (legacy format limit) | Download `.cif` instead |
+ | GraphQL response has `errors` array | Field name typo or wrong root | Read the error message; the API is strict about field names — check the schema browser at <https://data.rcsb.org/index.html#graphql-api> |
+
## Related Skills
- - **alphafold-database-access** -- AI-predicted structures; use when no experimental structure exists
- - **uniprot-protein-database** -- protein annotations, sequences, ID mapping (UniProt accession needed for AlphaFold)
- - **biopython-molecular-biology** -- parse downloaded PDB/mmCIF files, extract coordinates, compute distances
- - **autodock-vina-docking** -- downstream molecular docking using PDB structures as receptors
- - **rdkit-cheminformatics** -- analyze ligands extracted from PDB complexes
+ - **alphafold-database-access** — AI-predicted structures; use when no experimental structure exists
+ - **uniprot-protein-database** — protein annotations, sequences, ID mapping (UniProt accession needed for AlphaFold)
+ - **biopython-molecular-biology** — parse downloaded PDB/mmCIF files, extract coordinates, compute distances
+ - **autodock-vina-docking** — downstream molecular docking using PDB structures as receptors
+ - **rdkit-cheminformatics** — analyze ligands extracted from PDB complexes
## References
- - [RCSB PDB](https://www.rcsb.org) -- main portal and web search
- - [rcsb-api Python SDK documentation](https://rcsbapi.readthedocs.io/) -- official SDK docs
- - [rcsb-api GitHub repository](https://github.com/rcsb/py-rcsb-api) -- source code and issues
- - [RCSB PDB Web APIs overview](https://www.rcsb.org/docs/programmatic-access/web-apis-overview) -- REST, GraphQL, and search API docs
- - [Data API reference](https://data.rcsb.org/redoc/index.html) -- full endpoint documentation
+ - [RCSB PDB](https://www.rcsb.org) — main portal and web search
+ - [Search API v2 docs](https://search.rcsb.org/) — full JSON-payload reference and Swagger
+ - [Data API GraphQL browser](https://data.rcsb.org/index.html#graphql-api) — schema explorer
+ - [Search API attribute schema](https://search.rcsb.org/rcsbsearch/v2/metadata/schema) — JSON of every searchable attribute
+ - [RCSB PDB Web APIs overview](https://www.rcsb.org/docs/programmatic-access/web-apis-overview) — index of all programmatic surfaces
+ - For SDK-based usage, see the `rcsb-api` PyPI package; this SKILL.md uses the underlying REST/GraphQL directly so no SDK install is needed.