""" API client for interacting with the Modius Agent Performance API. """ import requests import logging from typing import List, Dict, Any, Optional from ..config.constants import API_BASE_URL, DATA_CONFIG from .models import ( AgentTypeResponse, AttributeDefinitionResponse, AgentsListResponse, AttributeValuesResponse ) logger = logging.getLogger(__name__) class ModiusAPIClient: """Client for interacting with the Modius Agent Performance API.""" def __init__(self, base_url: str = API_BASE_URL): self.base_url = base_url self.session = requests.Session() # Set default timeout and headers self.session.timeout = 30 self.session.headers.update({ 'User-Agent': 'Modius-Agent-Performance/1.0' }) def _make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """Make a request to the API and return the response.""" url = f"{self.base_url}{endpoint}" logger.debug(f"Making API request to: {url}") try: response = self.session.get(url, params=params) logger.debug(f"Response status: {response.status_code}") if response.status_code == 404: logger.warning(f"Resource not found: {url}") return {"error": "Not found", "status_code": 404} response.raise_for_status() return {"data": response.json(), "status_code": response.status_code} except requests.exceptions.RequestException as e: logger.error(f"API request error for {url}: {e}") return {"error": str(e), "status_code": getattr(e.response, 'status_code', 500)} def get_agent_type_by_name(self, type_name: str) -> AgentTypeResponse: """Get agent type by name.""" endpoint = f"/api/agent-types/name/{type_name}" result = self._make_request(endpoint) if "error" in result: return AgentTypeResponse({}, result["status_code"]) return AgentTypeResponse(result["data"], result["status_code"]) def get_attribute_definition_by_name(self, attr_name: str) -> AttributeDefinitionResponse: """Get attribute definition by name.""" endpoint = f"/api/attributes/name/{attr_name}" result = self._make_request(endpoint) if "error" in result: return AttributeDefinitionResponse({}, result["status_code"]) return AttributeDefinitionResponse(result["data"], result["status_code"]) def get_agents_by_type(self, type_id: int) -> AgentsListResponse: """Get all agents of a specific type.""" endpoint = f"/api/agent-types/{type_id}/agents/" result = self._make_request(endpoint) if "error" in result: return AgentsListResponse([], result["status_code"]) return AgentsListResponse(result["data"], result["status_code"]) def get_agent_attributes(self, agent_id: int, limit: int = None) -> AttributeValuesResponse: """Get attributes for a specific agent.""" endpoint = f"/api/agents/{agent_id}/attributes/" params = {"limit": limit or DATA_CONFIG["api_limit"]} result = self._make_request(endpoint, params) if "error" in result: return AttributeValuesResponse([], result["status_code"]) return AttributeValuesResponse(result["data"], result["status_code"]) def get_attribute_values_by_type_and_attr( self, agents: List[Dict[str, Any]], attr_def_id: int ) -> List[Dict[str, Any]]: """Get all attribute values for a specific attribute definition across all agents.""" all_attributes = [] logger.debug(f"Getting attributes for {len(agents)} agents with attr_def_id: {attr_def_id}") for agent in agents: agent_id = agent["agent_id"] # Get agent attributes response = self.get_agent_attributes(agent_id) if not response.is_success(): logger.error(f"Failed to get attributes for agent ID {agent_id}") continue agent_attrs = response.get_attribute_values() logger.debug(f"Agent {agent_id} has {len(agent_attrs)} attributes") # Filter for the specific attribute definition ID filtered_attrs = [ attr for attr in agent_attrs if attr.get("attr_def_id") == attr_def_id ] logger.debug(f"Agent {agent_id} has {len(filtered_attrs)} matching attributes") if filtered_attrs: logger.debug(f"Sample attribute for agent {agent_id}: {filtered_attrs[0]}") all_attributes.extend(filtered_attrs) logger.info(f"Total matching attributes found across all agents: {len(all_attributes)}") return all_attributes def close(self): """Close the session.""" self.session.close() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() # Utility functions for backward compatibility def get_agent_name(agent_id: int, agents: List[Dict[str, Any]]) -> str: """Get agent name from agent ID.""" for agent in agents: if agent["agent_id"] == agent_id: return agent["agent_name"] return "Unknown" # Global client instance (can be replaced with dependency injection later) _client_instance = None def get_api_client() -> ModiusAPIClient: """Get the global API client instance.""" global _client_instance if _client_instance is None: _client_instance = ModiusAPIClient() return _client_instance def close_api_client(): """Close the global API client instance.""" global _client_instance if _client_instance is not None: _client_instance.close() _client_instance = None