import httpx import logging from typing import Dict, List, Optional, Any, Union from app.config import COINCAP_API_BASE_URL, COINCAP_API_KEY logger = logging.getLogger(__name__) class CoinCapClient: """Client for interacting with the CoinCap API v3""" def __init__(self, api_key: str = COINCAP_API_KEY, base_url: str = COINCAP_API_BASE_URL): self.api_key = api_key self.base_url = base_url async def _request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict: """Make a request to the CoinCap API Args: method: HTTP method (GET, POST, etc.) endpoint: API endpoint (without base URL) params: Query parameters Returns: API response as dictionary """ if params is None: params = {} # Add API key to all requests params['apiKey'] = self.api_key url = f"{self.base_url}{endpoint}" try: async with httpx.AsyncClient() as client: response = await client.request(method, url, params=params) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: logger.error(f"HTTP error occurred: {e}") if e.response.status_code == 404: # Return empty data for 404 errors return {"data": []} raise except Exception as e: logger.error(f"Error in CoinCap API request: {e}") raise # Assets endpoints async def get_assets(self, search: Optional[str] = None, ids: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> Dict: """Get list of assets with optional filters""" params = {k: v for k, v in locals().items() if k not in ['self'] and v is not None} return await self._request("GET", "/assets", params) async def get_asset(self, slug: str) -> Dict: """Get information about a specific asset by slug""" return await self._request("GET", f"/assets/{slug}") async def get_asset_markets(self, slug: str, limit: Optional[int] = None, offset: Optional[int] = None) -> Dict: """Get markets for a specific asset""" params = {k: v for k, v in locals().items() if k not in ['self', 'slug'] and v is not None} return await self._request("GET", f"/assets/{slug}/markets", params) async def get_asset_history(self, slug: str, interval: str, start: Optional[int] = None, end: Optional[int] = None) -> Dict: """Get historical data for a specific asset Args: slug: Asset slug interval: Time interval (m1, m5, m15, m30, h1, h2, h6, h12, d1) start: Start time in milliseconds end: End time in milliseconds """ params = {k: v for k, v in locals().items() if k not in ['self', 'slug'] and v is not None} return await self._request("GET", f"/assets/{slug}/history", params) # Exchanges endpoints async def get_exchanges(self, limit: Optional[int] = None, offset: Optional[int] = None) -> Dict: """Get list of exchanges""" params = {k: v for k, v in locals().items() if k not in ['self'] and v is not None} return await self._request("GET", "/exchanges", params) async def get_exchange(self, exchange_id: str) -> Dict: """Get information about a specific exchange""" return await self._request("GET", f"/exchanges/{exchange_id}") # Markets endpoints async def get_markets(self, exchange_id: Optional[str] = None, base_symbol: Optional[str] = None, base_id: Optional[str] = None, quote_symbol: Optional[str] = None, quote_id: Optional[str] = None, asset_symbol: Optional[str] = None, asset_id: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> Dict: """Get list of markets with optional filters""" params = {k: v for k, v in locals().items() if k not in ['self'] and v is not None} return await self._request("GET", "/markets", params) # Rates endpoints async def get_rates(self, ids: Optional[str] = None) -> Dict: """Get conversion rates, optionally filtered by comma-separated slugs""" params = {k: v for k, v in locals().items() if k not in ['self'] and v is not None} return await self._request("GET", "/rates", params) async def get_rate(self, slug: str) -> Dict: """Get a specific conversion rate by slug""" return await self._request("GET", f"/rates/{slug}")