Source code for tooluniverse.cdc_tool

import requests
from typing import Dict, Any, Optional
from urllib.parse import urlencode
from .base_tool import BaseTool
from .tool_registry import register_tool

CDC_DATA_BASE_URL = "https://data.cdc.gov"


[docs] @register_tool("CDCRESTTool") class CDCRESTTool(BaseTool): """CDC Data.CDC.gov REST API tool (Socrata-based open data portal)."""
[docs] def __init__(self, tool_config): super().__init__(tool_config) self.endpoint_template = tool_config["fields"]["endpoint"]
[docs] def _build_url(self, arguments: Dict[str, Any]) -> str: """Build the full CDC Data API URL with path parameters and query string.""" endpoint = self.endpoint_template # Replace path parameters if "{dataset_id}" in endpoint: if "dataset_id" not in arguments: raise ValueError("dataset_id is required") endpoint = endpoint.replace("{dataset_id}", arguments["dataset_id"]) url = f"{CDC_DATA_BASE_URL}{endpoint}" # Build query parameters (Socrata API format) query_params = {} # Handle search query if "search_query" in arguments and arguments["search_query"]: query_params["$q"] = arguments["search_query"] # Handle category if "category" in arguments and arguments["category"]: query_params["category"] = arguments["category"] # A full SoQL query string ($query) must carry its own LIMIT/OFFSET/etc; # Socrata rejects mixing $query with the discrete $limit/$where/... params. soql_query = arguments.get("soql_query") # Handle limit ($limit in Socrata) limit = arguments.get("limit", 50) if limit and not soql_query: query_params["$limit"] = ( min(limit, 1000) if "views.json" in endpoint else min(limit, 50000) ) # Handle offset ($offset in Socrata) offset = arguments.get("offset", 0) if offset and not soql_query: query_params["$offset"] = offset # Handle WHERE clause if "where_clause" in arguments and arguments["where_clause"]: query_params["$where"] = arguments["where_clause"] # Handle ORDER BY if "order_by" in arguments and arguments["order_by"]: query_params["$order"] = arguments["order_by"] # Handle SoQL aggregation parameters (Socrata SODA $select/$group). # These only take effect on the /resource/{id}.json endpoint; the legacy # /api/views/{id}/rows.json endpoint silently ignores them. if "select_clause" in arguments and arguments["select_clause"]: query_params["$select"] = arguments["select_clause"] if "group_clause" in arguments and arguments["group_clause"]: query_params["$group"] = arguments["group_clause"] if "having_clause" in arguments and arguments["having_clause"]: query_params["$having"] = arguments["having_clause"] # Full SoQL query string ($query). When provided it overrides the other # SoQL clauses, so drop them to avoid Socrata "cannot mix" errors. if soql_query: for clause in ("$select", "$where", "$group", "$having", "$order"): query_params.pop(clause, None) query_params["$query"] = soql_query # Add query string if query_params: url += "?" + urlencode(query_params) return url
[docs] @staticmethod def _error(message: str) -> Dict[str, Any]: """Build a standard error envelope with a top-level ``error`` key. Keeps the legacy ``data.error`` field for backward compatibility while also exposing ``error`` at the top level so the return_schema oneOf error branch matches. """ return {"status": "error", "error": message, "data": {"error": message}}
[docs] def _make_request(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Make HTTP request to CDC Data API.""" try: url = self._build_url(arguments) except ValueError as e: return self._error(str(e)) try: resp = requests.get(url, timeout=30) resp.raise_for_status() data = resp.json() return { "status": "success", "data": data, "metadata": { "source": "CDC Data.CDC.gov", "endpoint": url.split("?")[0], "query": arguments, }, } except requests.exceptions.RequestException as e: detail = "" resp_obj = getattr(e, "response", None) if resp_obj is not None: detail = f" ({(resp_obj.text or '')[:300]})" return self._error(f"Request failed: {str(e)}{detail}") except ValueError as e: return self._error(f"Failed to parse JSON: {str(e)}")
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Execute the CDC Data tool.""" # Validate required parameters if "{dataset_id}" in self.endpoint_template: if "dataset_id" not in arguments: return self._error("dataset_id is required") return self._make_request(arguments)