""" Response formatting for API endpoints. """ import json from typing import Any, Dict, Optional, List from datetime import datetime from ..utils.logging import get_logger from ..utils.timing import get_current_timestamp logger = get_logger(__name__) class ResponseFormatter: """ Formats API responses with consistent structure and metadata. """ def __init__(self): """Initialize response formatter""" self.responses_formatted = 0 logger.info("Response formatter initialized") def success(self, data: Any, message: str = "Success", metadata: Optional[Dict] = None) -> Dict[str, Any]: """ Format successful response. Args: data: Response data message: Success message metadata: Additional metadata Returns: Dict: Formatted response """ response = { 'success': True, 'message': message, 'data': data, 'timestamp': get_current_timestamp().isoformat(), 'metadata': metadata or {} } self.responses_formatted += 1 return response def error(self, message: str, error_code: str = "UNKNOWN_ERROR", details: Optional[Dict] = None, status_code: int = 400) -> Dict[str, Any]: """ Format error response. Args: message: Error message error_code: Error code details: Error details status_code: HTTP status code Returns: Dict: Formatted error response """ response = { 'success': False, 'error': { 'message': message, 'code': error_code, 'details': details or {}, 'status_code': status_code }, 'timestamp': get_current_timestamp().isoformat() } self.responses_formatted += 1 return response def paginated(self, data: List[Any], page: int, page_size: int, total_count: int, message: str = "Success") -> Dict[str, Any]: """ Format paginated response. Args: data: Page data page: Current page number page_size: Items per page total_count: Total number of items message: Success message Returns: Dict: Formatted paginated response """ total_pages = (total_count + page_size - 1) // page_size pagination = { 'page': page, 'page_size': page_size, 'total_count': total_count, 'total_pages': total_pages, 'has_next': page < total_pages, 'has_previous': page > 1 } return self.success( data=data, message=message, metadata={'pagination': pagination} ) def heatmap_response(self, heatmap_data, symbol: str, exchange: Optional[str] = None) -> Dict[str, Any]: """ Format heatmap data response. Args: heatmap_data: Heatmap data symbol: Trading symbol exchange: Exchange name (None for consolidated) Returns: Dict: Formatted heatmap response """ if not heatmap_data: return self.error("Heatmap data not found", "HEATMAP_NOT_FOUND", status_code=404) # Convert heatmap to API format formatted_data = { 'symbol': heatmap_data.symbol, 'timestamp': heatmap_data.timestamp.isoformat(), 'bucket_size': heatmap_data.bucket_size, 'exchange': exchange, 'points': [ { 'price': point.price, 'volume': point.volume, 'intensity': point.intensity, 'side': point.side } for point in heatmap_data.data ] } metadata = { 'total_points': len(heatmap_data.data), 'bid_points': len([p for p in heatmap_data.data if p.side == 'bid']), 'ask_points': len([p for p in heatmap_data.data if p.side == 'ask']), 'data_type': 'consolidated' if not exchange else 'exchange_specific' } return self.success( data=formatted_data, message=f"Heatmap data for {symbol}", metadata=metadata ) def orderbook_response(self, orderbook_data, symbol: str, exchange: str) -> Dict[str, Any]: """ Format order book response. Args: orderbook_data: Order book data symbol: Trading symbol exchange: Exchange name Returns: Dict: Formatted order book response """ if not orderbook_data: return self.error("Order book not found", "ORDERBOOK_NOT_FOUND", status_code=404) # Convert order book to API format formatted_data = { 'symbol': orderbook_data.symbol, 'exchange': orderbook_data.exchange, 'timestamp': orderbook_data.timestamp.isoformat(), 'sequence_id': orderbook_data.sequence_id, 'bids': [ { 'price': bid.price, 'size': bid.size, 'count': bid.count } for bid in orderbook_data.bids ], 'asks': [ { 'price': ask.price, 'size': ask.size, 'count': ask.count } for ask in orderbook_data.asks ], 'mid_price': orderbook_data.mid_price, 'spread': orderbook_data.spread, 'bid_volume': orderbook_data.bid_volume, 'ask_volume': orderbook_data.ask_volume } metadata = { 'bid_levels': len(orderbook_data.bids), 'ask_levels': len(orderbook_data.asks), 'total_bid_volume': orderbook_data.bid_volume, 'total_ask_volume': orderbook_data.ask_volume } return self.success( data=formatted_data, message=f"Order book for {symbol}@{exchange}", metadata=metadata ) def metrics_response(self, metrics_data, symbol: str, exchange: str) -> Dict[str, Any]: """ Format metrics response. Args: metrics_data: Metrics data symbol: Trading symbol exchange: Exchange name Returns: Dict: Formatted metrics response """ if not metrics_data: return self.error("Metrics not found", "METRICS_NOT_FOUND", status_code=404) # Convert metrics to API format formatted_data = { 'symbol': metrics_data.symbol, 'exchange': metrics_data.exchange, 'timestamp': metrics_data.timestamp.isoformat(), 'mid_price': metrics_data.mid_price, 'spread': metrics_data.spread, 'spread_percentage': metrics_data.spread_percentage, 'bid_volume': metrics_data.bid_volume, 'ask_volume': metrics_data.ask_volume, 'volume_imbalance': metrics_data.volume_imbalance, 'depth_10': metrics_data.depth_10, 'depth_50': metrics_data.depth_50 } return self.success( data=formatted_data, message=f"Metrics for {symbol}@{exchange}" ) def status_response(self, status_data: Dict[str, Any]) -> Dict[str, Any]: """ Format system status response. Args: status_data: System status data Returns: Dict: Formatted status response """ return self.success( data=status_data, message="System status", metadata={'response_count': self.responses_formatted} ) def rate_limit_error(self, client_stats: Dict[str, float]) -> Dict[str, Any]: """ Format rate limit error response. Args: client_stats: Client rate limit statistics Returns: Dict: Formatted rate limit error """ return self.error( message="Rate limit exceeded", error_code="RATE_LIMIT_EXCEEDED", details={ 'remaining_tokens': client_stats['remaining_tokens'], 'reset_time': client_stats['reset_time'], 'requests_last_minute': client_stats['requests_last_minute'] }, status_code=429 ) def validation_error(self, field: str, message: str) -> Dict[str, Any]: """ Format validation error response. Args: field: Field that failed validation message: Validation error message Returns: Dict: Formatted validation error """ return self.error( message=f"Validation error: {message}", error_code="VALIDATION_ERROR", details={'field': field, 'message': message}, status_code=400 ) def to_json(self, response: Dict[str, Any], indent: Optional[int] = None) -> str: """ Convert response to JSON string. Args: response: Response dictionary indent: JSON indentation (None for compact) Returns: str: JSON string """ try: return json.dumps(response, indent=indent, ensure_ascii=False, default=str) except Exception as e: logger.error(f"Error converting response to JSON: {e}") return json.dumps(self.error("JSON serialization failed", "JSON_ERROR")) def get_stats(self) -> Dict[str, int]: """Get formatter statistics""" return { 'responses_formatted': self.responses_formatted } def reset_stats(self) -> None: """Reset formatter statistics""" self.responses_formatted = 0 logger.info("Response formatter statistics reset")