from typing import Any, Dict, List, Optional, Union from datetime import datetime import traceback from .logger import get_logger from .exceptions import FragmentaError logger = get_logger(__name__) class APIResponse: @staticmethod def success(data: Any = None, message: str = None, meta: Dict[str, Any] = None) -> Dict[str, Any]: response = { "success": True, "timestamp": datetime.utcnow().isoformat(), "data": data } if message: response["message"] = message if meta: response["meta"] = meta logger.debug(f"API Success Response: {message or 'Operation completed'}") return response @staticmethod def error( error: Union[str, Exception, FragmentaError], status_code: int = 500, details: Dict[str, Any] = None, include_traceback: bool = False ) -> Dict[str, Any]: if isinstance(error, FragmentaError): message = error.message error_details = error.details.copy() if error.details else {} if details: error_details.update(details) elif isinstance(error, Exception): message = str(error) error_details = { "type": type(error).__name__, **(details or {}) } else: message = str(error) error_details = details or {} response = { "success": False, "timestamp": datetime.utcnow().isoformat(), "error": { "message": message, "code": status_code, "details": error_details } } if include_traceback and isinstance(error, Exception): response["error"]["traceback"] = traceback.format_exc() logger.error(f"API Error Response ({status_code}): {message}") return response @staticmethod def validation_error(field_errors: Dict[str, List[str]]) -> Dict[str, Any]: return APIResponse.error( "Validation failed", status_code=400, details={ "validation_errors": field_errors, "total_errors": sum(len(errors) for errors in field_errors.values()) } ) @staticmethod def not_found(resource: str, identifier: str = None) -> Dict[str, Any]: message = f"{resource.title()} not found" if identifier: message += f": {identifier}" return APIResponse.error( message, status_code=404, details={ "resource_type": resource, "identifier": identifier } ) @staticmethod def unauthorized(message: str = "Authentication required") -> Dict[str, Any]: return APIResponse.error( message, status_code=401, details={"auth_required": True} ) @staticmethod def forbidden(message: str = "Access denied") -> Dict[str, Any]: return APIResponse.error( message, status_code=403, details={"access_denied": True} ) @staticmethod def progress( current: int, total: int, message: str = None, data: Any = None ) -> Dict[str, Any]: percentage = (current / total * 100) if total > 0 else 0 response = { "success": True, "timestamp": datetime.utcnow().isoformat(), "progress": { "current": current, "total": total, "percentage": round(percentage, 2), "completed": current >= total } } if message: response["progress"]["message"] = message if data: response["data"] = data return response def handle_api_error(func): def wrapper(*args, **kwargs): try: result = func(*args, **kwargs) if isinstance(result, dict) and "success" in result: return result return APIResponse.success(result) except FragmentaError as e: return APIResponse.error(e) except Exception as e: logger.exception(f"Unexpected error in {func.__name__}") return APIResponse.error( "Internal server error", status_code=500, details={"function": func.__name__} ) return wrapper def paginate_response( data: List[Any], page: int = 1, per_page: int = 10, total: int = None ) -> Dict[str, Any]: if total is None: total = len(data) start_idx = (page - 1) * per_page end_idx = start_idx + per_page page_data = data[start_idx:end_idx] total_pages = (total + per_page - 1) // per_page meta = { "pagination": { "page": page, "per_page": per_page, "total_items": total, "total_pages": total_pages, "has_next": page < total_pages, "has_prev": page > 1, "next_page": page + 1 if page < total_pages else None, "prev_page": page - 1 if page > 1 else None } } return APIResponse.success( data=page_data, meta=meta )