fragmenta / utils /api_responses.py
MazCodes's picture
Upload folder using huggingface_hub
63f0b06 verified
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
)