Source code for tooluniverse.three_d_beacons_tool

# three_d_beacons_tool.py
"""
3D Beacons API tool for ToolUniverse.

Provides access to the 3D Beacons Hub API, which aggregates 3D structure
models for proteins from multiple data providers including PDBe, AlphaFold DB,
SWISS-MODEL, PED (Protein Ensemble Database), AlphaFill, and isoform.io.

API: https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/
No authentication required. Free public access.
"""

import requests
from typing import Dict, Any
from collections import Counter
from .base_tool import BaseTool


BEACONS_BASE_URL = "https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api"


[docs] class ThreeDBeaconsTool(BaseTool): """ Tool for 3D Beacons Hub API providing aggregated 3D structure models from multiple providers for a given UniProt protein accession. Aggregates experimental structures (PDBe), predicted models (AlphaFold DB), homology models (SWISS-MODEL), conformational ensembles (PED), and more. No authentication required. """
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("timeout", 60) fields = tool_config.get("fields", {}) self.endpoint = fields.get("endpoint", "summary")
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Execute the 3D Beacons API call.""" try: return self._query(arguments) except requests.exceptions.Timeout: return {"error": f"3D Beacons API timed out after {self.timeout}s"} except requests.exceptions.ConnectionError: return {"error": "Failed to connect to 3D Beacons API"} except requests.exceptions.HTTPError as e: code = e.response.status_code if e.response is not None else "unknown" if code == 404: return { "error": f"No structures found for protein: {arguments.get('accession', '')}" } return {"error": f"3D Beacons API HTTP error: {code}"} except Exception as e: return {"error": f"Unexpected error querying 3D Beacons API: {str(e)}"}
[docs] def _query(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Route to appropriate endpoint.""" if self.endpoint == "summary": return self._get_structure_summary(arguments) elif self.endpoint == "structures": return self._get_structures(arguments) else: return {"error": f"Unknown endpoint: {self.endpoint}"}
[docs] def _get_structure_summary(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Get summary of all 3D structures/models for a UniProt protein.""" accession = arguments.get("accession", "") if not accession: return { "error": "accession parameter is required (UniProt accession, e.g., 'P04637')" } url = f"{BEACONS_BASE_URL}/uniprot/summary/{accession}.json" response = requests.get(url, timeout=self.timeout) response.raise_for_status() data = response.json() structures = data.get("structures", []) uniprot_entry = data.get("uniprot_entry", {}) # Compute provider and category statistics provider_counts = Counter( s.get("summary", {}).get("provider", "unknown") for s in structures ) category_counts = Counter( s.get("summary", {}).get("model_category", "unknown") for s in structures ) return { "data": { "accession": uniprot_entry.get("ac", accession), "sequence_length": uniprot_entry.get("sequence_length"), "total_structures": len(structures), "by_provider": dict(provider_counts), "by_category": dict(category_counts), }, "metadata": { "source": "3D Beacons Hub API", "accession": accession, }, }
[docs] def _get_structures(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Get detailed structure list for a UniProt protein.""" accession = arguments.get("accession", "") if not accession: return { "error": "accession parameter is required (UniProt accession, e.g., 'P04637')" } category_filter = arguments.get("category", None) provider_filter = arguments.get("provider", None) max_results = arguments.get("max_results", 20) url = f"{BEACONS_BASE_URL}/uniprot/summary/{accession}.json" response = requests.get(url, timeout=self.timeout) response.raise_for_status() data = response.json() structures = data.get("structures", []) uniprot_entry = data.get("uniprot_entry", {}) # Apply filters if category_filter: cat_upper = category_filter.upper() structures = [ s for s in structures if cat_upper in s.get("summary", {}).get("model_category", "").upper() ] if provider_filter: prov_lower = provider_filter.lower() structures = [ s for s in structures if prov_lower in s.get("summary", {}).get("provider", "").lower() ] total_matched = len(structures) structures = structures[:max_results] # Format results result_structures = [] for s in structures: sm = s.get("summary", {}) entry = { "model_identifier": sm.get("model_identifier"), "model_category": sm.get("model_category"), "provider": sm.get("provider"), "coverage": sm.get("coverage"), "model_url": sm.get("model_url"), "created": sm.get("created"), } # Add resolution for experimental structures if sm.get("resolution"): entry["resolution"] = sm.get("resolution") # Add confidence for predicted models if sm.get("confidence_avg_local_score"): entry["confidence_avg_local_score"] = sm.get( "confidence_avg_local_score" ) # Add sequence range if sm.get("uniprot_start"): entry["uniprot_start"] = sm.get("uniprot_start") entry["uniprot_end"] = sm.get("uniprot_end") # Add method for experimental if sm.get("experimental_method"): entry["experimental_method"] = sm.get("experimental_method") result_structures.append(entry) return { "data": { "accession": uniprot_entry.get("ac", accession), "sequence_length": uniprot_entry.get("sequence_length"), "total_matched": total_matched, "returned": len(result_structures), "structures": result_structures, }, "metadata": { "source": "3D Beacons Hub API", "accession": accession, "category_filter": category_filter, "provider_filter": provider_filter, }, }