Files
TimeTracker/app/utils/api_responses.py
Dries Peeters 90dde470da style: standardize code formatting and normalize line endings
- Normalize line endings from CRLF to LF across all files to match .editorconfig
- Standardize quote style from single quotes to double quotes
- Normalize whitespace and formatting throughout codebase
- Apply consistent code style across 372 files including:
  * Application code (models, routes, services, utils)
  * Test files
  * Configuration files
  * CI/CD workflows

This ensures consistency with the project's .editorconfig settings and
improves code maintainability.
2025-11-28 20:05:37 +01:00

215 lines
5.1 KiB
Python

"""
Consistent API response helpers.
Provides standardized response formats for all API endpoints.
"""
from typing import Any, Dict, Optional, List
from flask import jsonify, Response
from marshmallow import ValidationError
def success_response(
data: Any = None, message: Optional[str] = None, status_code: int = 200, meta: Optional[Dict[str, Any]] = None
) -> Response:
"""
Create a successful API response.
Args:
data: Response data
message: Optional success message
status_code: HTTP status code
meta: Optional metadata
Returns:
Flask JSON response
"""
response = {
"success": True,
}
if message:
response["message"] = message
if data is not None:
response["data"] = data
if meta:
response["meta"] = meta
return jsonify(response), status_code
def error_response(
message: str,
error_code: Optional[str] = None,
status_code: int = 400,
errors: Optional[Dict[str, List[str]]] = None,
details: Optional[Dict[str, Any]] = None,
) -> Response:
"""
Create an error API response.
Args:
message: Error message
error_code: Optional error code
status_code: HTTP status code
errors: Optional field-specific errors
details: Optional additional error details
Returns:
Flask JSON response
"""
response = {"success": False, "error": error_code or "error", "message": message}
if errors:
response["errors"] = errors
if details:
response["details"] = details
return jsonify(response), status_code
def validation_error_response(errors: Dict[str, List[str]], message: str = "Validation failed") -> Response:
"""
Create a validation error response.
Args:
errors: Field-specific validation errors
message: Error message
Returns:
Flask JSON response
"""
return error_response(message=message, error_code="validation_error", status_code=400, errors=errors)
def not_found_response(resource: str = "Resource", resource_id: Optional[Any] = None) -> Response:
"""
Create a not found error response.
Args:
resource: Resource type name
resource_id: Optional resource ID
Returns:
Flask JSON response
"""
message = f"{resource} not found"
if resource_id is not None:
message = f"{resource} with ID {resource_id} not found"
return error_response(message=message, error_code="not_found", status_code=404)
def unauthorized_response(message: str = "Authentication required") -> Response:
"""
Create an unauthorized error response.
Args:
message: Error message
Returns:
Flask JSON response
"""
return error_response(message=message, error_code="unauthorized", status_code=401)
def forbidden_response(message: str = "Insufficient permissions") -> Response:
"""
Create a forbidden error response.
Args:
message: Error message
Returns:
Flask JSON response
"""
return error_response(message=message, error_code="forbidden", status_code=403)
def paginated_response(
items: List[Any], page: int, per_page: int, total: int, message: Optional[str] = None
) -> Response:
"""
Create a paginated response.
Args:
items: List of items for current page
page: Current page number
per_page: Items per page
total: Total number of items
message: Optional message
Returns:
Flask JSON response
"""
pages = (total + per_page - 1) // per_page if total > 0 else 0
pagination = {
"page": page,
"per_page": per_page,
"total": total,
"pages": pages,
"has_next": page < pages,
"has_prev": page > 1,
"next_page": page + 1 if page < pages else None,
"prev_page": page - 1 if page > 1 else None,
}
return success_response(data=items, message=message, meta={"pagination": pagination})
def handle_validation_error(error: ValidationError) -> Response:
"""
Handle Marshmallow validation errors.
Args:
error: ValidationError instance
Returns:
Flask JSON response
"""
errors = {}
if isinstance(error.messages, dict):
errors = error.messages
elif isinstance(error.messages, list):
errors = {"_general": error.messages}
return validation_error_response(errors=errors)
def created_response(data: Any, message: Optional[str] = None, location: Optional[str] = None) -> Response:
"""
Create a 201 Created response.
Args:
data: Created resource data
message: Optional success message
location: Optional resource location URL
Returns:
Flask JSON response
"""
response_data = {"data": data}
if message:
response_data["message"] = message
response = jsonify(response_data)
response.status_code = 201
if location:
response.headers["Location"] = location
return response
def no_content_response() -> Response:
"""
Create a 204 No Content response.
Returns:
Flask response
"""
return "", 204