Source code for tooluniverse.ncbi_eutils_tool

"""
NCBI E-utilities Tool with Rate Limiting

This module provides a base class for NCBI E-utilities API tools with
built-in rate limiting and retry logic to handle 429 errors.
"""

import time
import requests
from typing import Dict, Any, Optional
from .base_tool import BaseTool


[docs] class NCBIEUtilsTool(BaseTool): """Base class for NCBI E-utilities tools with rate limiting."""
[docs] def __init__(self, tool_config): super().__init__(tool_config) self.base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils" self.last_request_time = 0 self.min_interval = 0.34 # ~3 requests/second (NCBI limit without API key) self.max_retries = 3 self.initial_retry_delay = 1 self.session = requests.Session() self.session.headers.update( {"Accept": "application/json", "User-Agent": "ToolUniverse/1.0"} ) self.timeout = 30
[docs] def _make_request( self, endpoint: str, params: Optional[Dict] = None ) -> Dict[str, Any]: """Make request with rate limiting and retry logic.""" url = f"{self.base_url}{endpoint}" for attempt in range(self.max_retries): # Rate limiting elapsed = time.time() - self.last_request_time if elapsed < self.min_interval: time.sleep(self.min_interval - elapsed) try: response = self.session.get(url, params=params, timeout=self.timeout) self.last_request_time = time.time() response.raise_for_status() # Try to parse JSON response try: data = response.json() except ValueError: # If not JSON, return text data = response.text return { "status": "success", "data": data, "url": url, "content_type": response.headers.get( "content-type", "application/json" ), } except requests.exceptions.HTTPError as e: if e.response.status_code == 429 and attempt < self.max_retries - 1: # Exponential backoff for rate limiting delay = self.initial_retry_delay * (2**attempt) print( f"Rate limited, retrying in {delay} seconds... (attempt {attempt + 1}/{self.max_retries})" ) time.sleep(delay) continue else: return { "status": "error", "error": f"NCBI E-utilities API request failed: {str(e)}", "url": url, "status_code": ( e.response.status_code if hasattr(e, "response") else None ), } except requests.exceptions.RequestException as e: return { "status": "error", "error": f"NCBI E-utilities API request failed: {str(e)}", "url": url, } return { "status": "error", "error": f"NCBI E-utilities API request failed after {self.max_retries} attempts", "url": url, }
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Execute the tool with given arguments.""" return self._make_request(self.endpoint, arguments)