
Pdb Database
- 1.3k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
PDB Database is a Claude skill that downloads protein structure coordinate files in mmCIF or PDB format from the RCSB Protein Data Bank for developers building scientific or AI research prototypes that need structural bi
About
PDB Database is a Google DeepMind science skill that downloads PDB coordinate files in mmCIF or PDB format from RCSB. The bundled script requires Python 3.10 or newer and fetches specific protein structures for scientific workflows or AI research prototypes. Developers reach for PDB Database when they need authoritative structural coordinates from the Protein Data Bank without writing custom RCSB download logic. The skill fits quick retrieval of named structures into local pipelines, notebooks, or model-training prep steps.
- Downloads mmCIF or legacy PDB coordinate files from RCSB
- Accepts comma-separated list of PDB IDs
- Automatic ID sanitization and formatting (pdb_0000 prefix)
- Rate-limited HTTP client (5 QPS) to respect server limits
- Built on scienceskillscommon library for consistent scientific tooling
Pdb Database by the numbers
- 1,318 all-time installs (skills.sh)
- +167 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #236 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill pdb-databaseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you download protein structures from RCSB PDB?
Quickly fetch specific protein structure files from the RCSB PDB database for scientific or AI research prototypes.
Who is it for?
Developers or researchers building bioinformatics tools, structural biology prototypes, or protein-aware ML pipelines who need RCSB coordinate files programmatically.
Skip if: Developers working on unrelated domains who do not need protein structure coordinates or RCSB integration.
When should I use this skill?
The user needs to fetch, download, or load a specific protein structure file from RCSB PDB in mmCIF or PDB format.
What you get
Local mmCIF or PDB coordinate files for specified protein structures retrieved from RCSB.
- mmCIF files
- PDB coordinate files
By the numbers
- Requires Python 3.10 or newer
Files
RCSB Protein Data Bank skill
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the terms at https://www.rcsb.org/pages/usage-policy, then (2) create the file recording the notification text and timestamp.
Core Rules
- Always prefer to use the provided scripts. Only as a last resort use
curl, urllib, raw HTTP requests, or any other method to access PDB APIs. The scripts automatically enforce required rate limits.
- Always redirect output to a file. Parse output with e.g.
jq,grep,
or a short Python snippet. Do NOT print large API responses to stdout to avoid truncation.
- Notification: If this skill is used, ensure this is mentioned in the
output.
- Explain your queries On completing a task that used PDB JSON/GraphQL
queries, explain in clear language what your query did so the user can correct any bad assumptions.
Attribute-based search workflow
1. Fetch the relevant schema to discover searchable attribute names. For structure attributes: uv run scripts/fetch_schema.py --api search_structure --output schema_structure.txt For chemical attributes: uv run scripts/fetch_schema.py --api search_chemical --output schema_chemical.txt
2. Grep the schema to find relevant attributes. Grep one keyword at a time and examine many lines — there are lots of similar attributes and you must choose the best match for the user's intent.
3. Compose and run a JSON search query using the discovered attributes: uv run scripts/search_pdb.py --query '<JSON>' --return_type <RETURN_TYPE> --output results.json Pass the --count_only flag to get just the number of matching entries.
For step 2: some basic PDB concepts (helpful for attribute choice)
- Entity: A unique molecule found in a structure.
- Instance / Chain: A particular copy of an entity. E.g. if a structure
contains two protein chains with the same sequence, they are the same entity but different instances / chains.
- Assembly: A biologically relevant collection of instances / chains. This
may be the same as the deposited structure, a subset, or multiple copies.
- Label vs Auth: Polymer instances get letter labels ("A", "B", "AA") and
their monomers are numbered. There are author-assigned ("auth") and PDB-internal ("label") schemes. The label scheme is more consistent and is always used in scripts and APIs. However, users and papers may refer to the author scheme (clarify which scheme is being used if necessary).
- Chemical component: A small molecule / monomer, with an ID matching
[A-Z]{1,3}
- Primary citation: The main publication about a structure. Prefer
primary_citation attributes over citation attributes.
- Resolution: Frequently used measure of structure quality (lower is
better). Usually prefer rcsb_entry_info.resolution_combined, which accounts for different experimental methods.
For step 3: Example queries
# Non-human proteins published in Nature, newest first
uv run scripts/search_pdb.py --query '{ "type": "group", "logical_operator": "and", "nodes": [ { "type": "terminal", "service": "text", "parameters": { "operator": "exact_match", "negation": true, "value": "Homo sapiens", "attribute": "rcsb_entity_source_organism.taxonomy_lineage.name" } }, { "type": "terminal", "service": "text", "parameters": { "operator": "exact_match", "value": "Nature", "attribute": "rcsb_primary_citation.rcsb_journal_abbrev" } } ] }' --return_type entry --sort_by rcsb_accession_info.initial_release_date --sort_direction desc --page_start 0 --rows 100 --output results.json# Structures containing the chemical component CA (Ca2+ ion)
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "text_chem", "parameters": { "operator": "exact_match", "value": "CA", "attribute": "rcsb_chem_comp_container_identifiers.comp_id" } }' --return_type entry --output results.json# Number of entries with disulfide bonds
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "text", "parameters": { "operator": "exact_match", "value": "disulfide bridge", "attribute": "rcsb_polymer_struct_conn.connect_type" } }' --return_type entry --count-only --output count.jsonCommon operators: exact_match, equals, exists, contains_phrase, contains_words, in, greater, less
Similarity-based search workflow
Similarity searches do not require a schema fetch. Basic examples:
# Sequence similarity
uv run scripts/search_pdb.py --query '{ "query": { "type": "terminal", "service": "sequence", "parameters": { "evalue_cutoff": 1, "identity_cutoff": 0.9, "sequence_type": "protein", "value": "MTEYKLVVVGAGGVGKSALTIQLIQNHFVDEYDPTIEDSYRKQ" } }, "request_options": { "scoring_strategy": "sequence" } }' --return_type polymer_entity --output results.json# Structure similarity
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "structure", "parameters": { "value": {"entry_id": "6LU7", "asym_id": "A"}, "number_of_candidates": 2000 } }' --return_type polymer_entity --output results.json# Sequence motif match
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "seqmotif", "parameters": { "value": "C-x(2,4)-C-x(3)-[LIVMFYWC]-x(8)-H-x(3,5)-H.", "pattern_type": "prosite", "sequence_type": "protein" } }' --return_type polymer_entity --output results.json# Chemical descriptor match
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "chemical", "parameters": { "value": "InChI=1S/C8H9NO2/c1-6(10)9-7-2-4-8(11)5-3-7/h2-5,11H,1H3,(H,9,10)", "type": "descriptor", "descriptor_type": "InChI", "match_type": "graph-strict" } }' --return_type mol_definition --output results.jsonSee https://search.rcsb.org/#search-services for more details.
Full text search workflow
Searches all text associated with an entry. Example:
uv run scripts/search_pdb.py --query '{ "type": "terminal", "service": "full_text", "parameters": { "value": "isopeptide + ( collagen | fibrinogen )" } }' --return_type entry --output results.jsonImportant: use full_text search as a last resort when there's nomore precise attribute search available. Consider using the struct.title orrcsb_pubmed_abstract_text attributes instead.File download workflow
To download full PDB entries, use the download_coordinate_files.py script. Use this when you need access to atomic coordinates, when asked for a pdb / mmcif file, or when non-specifically asked to fetch a PDB code. Example:
uv run scripts/download_coordinate_files.py --ids "4HHB,6BEA" --format "mmcif" --output_dir <OUTPUT_DIR>Metadata query workflow
This flow is significantly more efficient than downloading full coordinate files when you only need a few pieces of metadata about each entry / entity.
1. Fetch the schema for the relevant object type. E.g. uv run scripts/fetch_schema.py --api data_entry --output schema_entry.txt
2. Grep the schema for relevant fields (one keyword at a time, many lines).
3. Compose and run a GraphQL metadata query: uv run scripts/fetch_pdb_metadata.py --query '<GraphQL>' --output results.json
For step 3: Example queries
# Fetch structure titles and experimental methods
uv run scripts/fetch_pdb_metadata.py --query '{ entries(entry_ids: ["1STP", "2JEF", "1CDG"]) { rcsb_id struct { title } exptl { method } } }' --output results.json# Fetch polymer entity taxonomy and cluster membership
uv run scripts/fetch_pdb_metadata.py --query '{ polymer_entities(entity_ids:["2CPK_1","3WHM_1","2D5Z_1"]) { rcsb_id rcsb_entity_source_organism { ncbi_taxonomy_id ncbi_scientific_name } rcsb_cluster_membership { cluster_id identity } } }' --output results.json# Fetch polymer entity external sequence database accessions
uv run scripts/fetch_pdb_metadata.py --query '{ entries(entry_ids:["7NHM", "5L2G"]){ polymer_entities { rcsb_id rcsb_polymer_entity_container_identifiers { reference_sequence_identifiers { database_accession database_name } } } } }' --output results.json# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Downloads PDB coordinate files (mmCIF or PDB) from RCSB."""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
import os
import sys
from science_skills.skills.scienceskillscommon import http_client
CLIENT = http_client.HttpClient("https://files-beta.rcsb.org", qps=5.0)
def sanitize_id(pdb_id: str) -> str:
"""Sanitize PDB ID."""
pdb_id = pdb_id.lower().strip()
if len(pdb_id) == 4:
return f"pdb_0000{pdb_id}"
return pdb_id
def download_files(args: argparse.Namespace):
"""Downloads coordinate files."""
ids = [id.strip() for id in args.ids.split(",") if id.strip()]
if len(ids) > 1_000:
print(
"Aborting: this script is not recommended for bulk downloads."
"**Check with the user**, then consider downloading a copy of all"
f" {args.format} files using the recommended bulk script at "
"https://cdn.rcsb.org/wwpdb/docs/BetaArchiveBatchDownloader.py.",
file=sys.stderr,
)
return
# RCSB bulk download script suggests a higher QPS may be acceptable here (~5).
estimated_time_secs = len(ids) / 5.0
print(f"Estimated download time: {estimated_time_secs / 60:.1f} minutes).")
if not os.path.exists(args.output_dir):
os.makedirs(args.output_dir, exist_ok=True)
for pdb_id in ids:
sanitized_id = sanitize_id(pdb_id)
shard_chars = sanitized_id[-3:-1]
ext = "cif" if args.format == "mmcif" else "pdb"
url = (
f"/pub/wwpdb/pdb/data/entries/{shard_chars}/{sanitized_id}/"
f"structures/{sanitized_id}.{ext}.gz"
)
output_path = os.path.join(args.output_dir, f"{sanitized_id}.{ext}.gz")
print(f"Downloading {sanitized_id} from {url}...", file=sys.stderr)
try:
content = CLIENT.fetch_bytes(url)
with open(output_path, "wb") as f:
f.write(content)
print(f"Saved to {output_path}", file=sys.stderr)
except Exception as e:
print(f"Failed to download {pdb_id}: {e}", file=sys.stderr)
def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description="Download PDB coordinate files")
parser.add_argument(
"--format",
type=str,
required=True,
choices=["mmcif", "pdb"],
help="File format to download (mmcif or pdb)",
)
parser.add_argument(
"--ids",
type=str,
required=True,
help="Comma-separated list of PDB IDs",
)
parser.add_argument(
"--output_dir",
type=str,
required=True,
help="Directory to save files to",
)
return parser.parse_args()
if __name__ == "__main__":
main_args = parse_args()
download_files(main_args)
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Gets PDB data using the RCSB Data API (GraphQL)."""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
import sys
import urllib.parse
from science_skills.skills.scienceskillscommon import http_client
CLIENT = http_client.HttpClient("https://data.rcsb.org", qps=2.0)
def get_pdb_metadata(args: argparse.Namespace):
"""Executes a GraphQL query against the PDB Data API.
Args:
args: parsed command line arguments containing the query.
"""
encoded_query = urllib.parse.quote(args.query.strip())
url = f"https://data.rcsb.org/graphql?query={encoded_query}"
print(f"Querying PDB Data API from {url}...", file=sys.stderr)
content = CLIENT.fetch_bytes(url)
with open(args.output, "w") as f:
f.write(content.decode("utf-8"))
def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Get PDB data using the RCSB Data API (GraphQL)"
)
parser.add_argument(
"--query",
type=str,
required=True,
help="GraphQL query string",
)
parser.add_argument(
"--output",
type=str,
required=True,
help="File to write the output to",
)
return parser.parse_args()
if __name__ == "__main__":
main_args = parse_args()
get_pdb_metadata(main_args)
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Fetches JSON PDB schemas and saves a greppable list of properties."""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
from typing import Any
from science_skills.skills.scienceskillscommon import http_client
SEARCH_CLIENT = http_client.HttpClient("https://search.rcsb.org", qps=2.0)
DATA_CLIENT = http_client.HttpClient("https://data.rcsb.org", qps=2.0)
# Mapping of API types to their corresponding schema URLs
API_URLS = {
"search_structure": "https://search.rcsb.org/rcsbsearch/v2/metadata/schema",
"search_chemical": (
"https://search.rcsb.org/rcsbsearch/v2/metadata/chemical/schema"
),
"data_entry": "https://data.rcsb.org/rest/v1/schema/entry",
"data_polymer_entity": (
"https://data.rcsb.org/rest/v1/schema/polymer_entity"
),
"data_polymer_entity_instance": (
"https://data.rcsb.org/rest/v1/schema/polymer_entity_instance"
),
"data_assembly": "https://data.rcsb.org/rest/v1/schema/assembly",
"data_non_polymer_entity": (
"https://data.rcsb.org/rest/v1/schema/nonpolymer_entity"
),
"data_non_polymer_entity_instance": (
"https://data.rcsb.org/rest/v1/schema/nonpolymer_entity_instance"
),
"data_branched_entity_instance": (
"https://data.rcsb.org/rest/v1/schema/branched_entity_instance"
),
"data_branched_entity": (
"https://data.rcsb.org/rest/v1/schema/branched_entity"
),
"data_chemical_component": "https://data.rcsb.org/rest/v1/schema/chem_comp",
}
def _collect_definitions(
schema: dict[str, Any], prefix: str, config: argparse.Namespace
) -> set[str]:
"""Traverses a composite schema node and collects property definitions."""
results = set()
if "anyOf" in schema:
for sub in schema["anyOf"]:
results |= _collect_definitions(sub, prefix, config)
if "oneOf" in schema:
for sub in schema["oneOf"]:
results |= _collect_definitions(sub, prefix, config)
if "allOf" in schema:
for sub in schema["allOf"]:
results |= _collect_definitions(sub, prefix, config)
type_val = schema.get("type")
# Handle implicit object type if 'properties' is present
if type_val == "object" or "properties" in schema:
props = schema.get("properties", {})
for key, val in props.items():
full_name = f"{prefix}.{key}" if prefix else key
results |= _process_property_node(val, full_name, config)
# Handle implicit array type if 'items' is present
elif type_val == "array" or "items" in schema:
items = schema.get("items")
if items:
# Prefix doesn't include array_name.items, so no prefix update
results |= _collect_definitions(items, prefix, config)
return results
def _process_property_node(
node: dict[str, Any], full_name: str, config: argparse.Namespace
) -> set[str]:
"""Processes an individual schema node and collects property definitions."""
results = set()
# Handle composite types first
if "anyOf" in node:
for sub in node["anyOf"]:
results |= _process_property_node(sub, full_name, config)
if "oneOf" in node:
for sub in node["oneOf"]:
results |= _process_property_node(sub, full_name, config)
if "allOf" in node:
for sub in node["allOf"]:
results |= _process_property_node(sub, full_name, config)
type_val = node.get("type")
# Normalize type to list
types = []
if isinstance(type_val, list):
types = type_val
elif type_val:
types = [type_val]
is_object = "object" in types or "properties" in node
is_array = "array" in types or "items" in node
# Check if it has any primitive aspect
# A property is primitive if it has a type that is NOT object or array
has_primitive = False
for t in types:
if t not in ["object", "array"]:
has_primitive = True
# For arrays of primitives, metadata might be stored in 'items'
meta_node = node
if is_array and "items" in node and isinstance(node["items"], dict):
if "rcsb_search_context" in node["items"] or "description" in node["items"]:
meta_node = node["items"]
# Output logic
should_output = False
if config.searchable_only:
if "rcsb_search_context" in meta_node or "rcsb_search_context" in node:
should_output = True
else:
if "description" in meta_node or has_primitive:
should_output = True
if should_output:
description = (
meta_node.get("description", node.get("description", ""))
.replace("\n", " ")
.strip()
)
# Truncate description if requested
if (
config.truncate_description > 0
and len(description) > config.truncate_description
):
description = description[: config.truncate_description] + "..."
enum_values = meta_node.get("enum", node.get("enum"))
enum_str = ""
if enum_values:
# Make a copy to avoid modifying original if we truncate
vals = list(enum_values)
if config.truncate_enums > 0 and len(vals) > config.truncate_enums:
# Keep first N items and append "..."
vals = vals[: config.truncate_enums] + ["..."]
enum_str = f" {vals}"
prop_type = node.get("type", "")
if is_array and not prop_type:
prop_type = "array"
results.add(f"{full_name} ({prop_type}): {description}{enum_str}")
# Recurse if it has object/array structure
if is_object or is_array:
results |= _collect_definitions(node, full_name, config)
return results
def main():
parser = argparse.ArgumentParser(
description="Fetch JSON Schema and save a greppable list of properties."
)
parser.add_argument(
"--api",
type=str,
choices=list(API_URLS.keys()),
help="Api to use. Options: " + ",".join(list(API_URLS.keys())),
)
parser.add_argument(
"--truncate-description",
type=int,
default=100,
help=(
"Number of characters to truncate description to (default: 100). Set"
" to 0 to disable."
),
)
parser.add_argument(
"--truncate-enums",
type=int,
default=0,
help="Number of enum items to truncate to (default: 0 = no truncation).",
)
parser.add_argument(
"--output",
type=str,
required=True,
help="File to write the schema to.",
)
args = parser.parse_args()
if "search" in args.api:
args.searchable_only = True
else:
args.searchable_only = False
if args.api:
url = API_URLS[args.api]
client = DATA_CLIENT if args.api.startswith("data_") else SEARCH_CLIENT
print(f"Fetching schema from {url}...")
try:
data = client.fetch_json(url, timeout=30)
except http_client.HttpError as e:
print(f"Error fetching data from URL: {e}")
return
else:
print("Error: No API type specified.")
return
# Start traversal
results = _collect_definitions(schema=data, prefix="", config=args)
# Sort and write
sorted_lines = sorted(results)
with open(args.output, "w") as f:
for line in sorted_lines:
f.write(line + "\n")
print(
f"Processed {len(sorted_lines)} properties. Output written to"
f" {args.output}"
)
if __name__ == "__main__":
main()
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Searches PDB using the RCSB Search API v2.
This script allows executing structured queries against the PDB Search API
with tunable pagination, return types, and sorting.
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
import json
import sys
import urllib.parse
from science_skills.skills.scienceskillscommon import http_client
CLIENT = http_client.HttpClient("https://search.rcsb.org", qps=2.0)
def search_pdb(args: argparse.Namespace):
"""Executes a search against the PDB Search API.
Args:
args: parsed command line arguments containing the query and options.
"""
try:
parsed_query = json.loads(args.query)
except json.JSONDecodeError as e:
print(f"Error parsing --query as JSON: {e}", file=sys.stderr)
sys.exit(1)
if isinstance(parsed_query, dict) and "query" in parsed_query:
# Payload already contains "query" key, it's a full request payload
payload = parsed_query
else:
# Only "query" block provided
payload = {"query": parsed_query}
if args.return_type is not None:
payload["return_type"] = args.return_type
request_options = payload.get("request_options", {})
if args.count_only:
# Count-only mode: request 0 rows, just get total_count from response
request_options["paginate"] = {"start": 0, "rows": 0}
# Remove return_all_hits if present, since we don't want all results
request_options.pop("return_all_hits", None)
elif args.page_start is not None or args.rows is not None:
# Remove return_all_hits so CLI pagination flags are not silently ignored.
request_options.pop("return_all_hits", None)
paginate = request_options.get("paginate", {})
if args.page_start is not None:
paginate["start"] = args.page_start
if args.rows is not None:
paginate["rows"] = args.rows
request_options["paginate"] = paginate
else:
# Default behavior: return all hits if no pagination is specified
request_options["return_all_hits"] = True
if args.sort_by is not None:
sort_item = {"sort_by": args.sort_by}
if args.sort_direction is not None:
sort_item["direction"] = args.sort_direction
request_options["sort"] = [sort_item]
if request_options:
payload["request_options"] = request_options
json_payload = json.dumps(payload, separators=(",", ":"))
encoded_query = urllib.parse.quote(json_payload)
url = f"https://search.rcsb.org/rcsbsearch/v2/query?json={encoded_query}"
print(f"Querying PDB Search API from {url}...", file=sys.stderr)
content = CLIENT.fetch_bytes(url)
if args.count_only:
# Parse the response to extract just the total count
response_data = json.loads(content.decode("utf-8"))
total_count = response_data.get("total_count", 0)
count_result = {"total_count": total_count}
print(f"Total count: {total_count}", file=sys.stderr)
with open(args.output, "w") as f:
json.dump(count_result, f, indent=2)
else:
with open(args.output, "w") as f:
f.write(content.decode("utf-8"))
def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Search PDB using the RCSB Search API v2"
)
parser.add_argument(
"--query",
type=str,
required=True,
help="JSON string of the query object or full request payload",
)
parser.add_argument(
"--return_type",
type=str,
required=True,
choices=[
"entry",
"assembly",
"polymer_entity",
"non_polymer_entity",
"polymer_instance",
"mol_definition",
],
help=(
"Type of returned object."
"entry = [PDB ID]"
"assembly = [PDB ID]-[ASSEMBLY ID]"
"polymer_entity = [PDB ID]-[ENTITY ID]"
"non_polymer_entity = [PDB ID]-[ENTITY ID]"
"polymer_instance = [PDB ID].[LABEL ASYM ID]"
"mol_definition = [CHEMICAL COMP ID] or [BIRD ID]"
),
)
parser.add_argument(
"--sort_by",
type=str,
help=(
"Attribute to sort by (commonly score or "
"rcsb_accession_info.initial_release_date)"
),
)
parser.add_argument(
"--sort_direction",
type=str,
choices=["asc", "desc"],
help="Sort direction (used with --sort_by)",
)
parser.add_argument(
"--page_start",
type=int,
help="Start index for pagination",
)
parser.add_argument(
"--rows",
type=int,
help="Number of rows to return",
)
parser.add_argument(
"--count-only",
action="store_true",
help=(
"Return only the total count of matching entries, not the full"
" result list. Useful when you need to know how many results match"
" without downloading them all."
),
)
parser.add_argument(
"--output",
type=str,
required=True,
help="File to write the output to",
)
return parser.parse_args()
if __name__ == "__main__":
main_args = parse_args()
search_pdb(main_args)
Related skills
FAQ
Which file formats does PDB Database download?
PDB Database downloads coordinate files from RCSB in mmCIF or classic PDB format. Developers specify the target structure and receive authoritative coordinate files for downstream scientific or AI research workflows.
What Python version does PDB Database require?
PDB Database requires Python 3.10 or newer for its bundled download script. The skill targets quick retrieval of specific RCSB structures without hand-writing fetch and parse logic.
Is Pdb Database safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.