Spaces:
Running
Running
File size: 4,187 Bytes
a04c9e9 f32824f a04c9e9 f32824f a04c9e9 f32824f a04c9e9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
import time
import json
import logging
import requests
from datetime import datetime
from functools import wraps
from typing import Dict, Any
logger = logging.getLogger(__name__)
def with_error_handling(func):
"""Decorator to add comprehensive error handling."""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
try:
safe_args = []
for i, arg in enumerate(args[:2]):
if isinstance(arg, (str, int, float, bool)):
safe_args.append(str(arg))
else:
safe_args.append(f"<{type(arg).__name__}>")
logger.info(f"Starting {func.__name__} with args: {safe_args}")
result = func(*args, **kwargs)
if isinstance(result, dict) and 'error' not in result:
result = standardize_response(result, func.__name__)
execution_time = time.time() - start_time
logger.info(f"Completed {func.__name__} in {execution_time:.2f}s")
return result
except requests.exceptions.Timeout:
logger.error(f"Timeout in {func.__name__}")
return create_error_response("Request timeout", func.__name__)
except requests.exceptions.ConnectionError:
logger.error(f"Connection error in {func.__name__}")
return create_error_response("Connection failed", func.__name__)
except requests.exceptions.RequestException as e:
logger.error(f"Request error in {func.__name__}: {e}")
return create_error_response(f"Request failed: {str(e)}", func.__name__)
except Exception as e:
logger.error(f"Unexpected error in {func.__name__}: {e}")
return create_error_response(f"Unexpected error: {str(e)}", func.__name__)
return wrapper
def standardize_response(data: Dict[str, Any], source: str) -> Dict[str, Any]:
"""Standardize API response format with metadata."""
# Check if data is already a standardized response to avoid double nesting
if isinstance(data, dict) and "data" in data and "metadata" in data and "status" in data:
return data
return {
"data": data,
"metadata": {
"source": source,
"timestamp": datetime.now().isoformat(),
"version": "1.1.0",
"cached": False
},
"status": "success"
}
def create_error_response(error_msg: str, source: str, error_code: str = None) -> Dict[str, Any]:
"""Create standardized error response with optional error code."""
response = {
"data": None,
"metadata": {
"source": source,
"timestamp": datetime.now().isoformat(),
"version": "1.1.0"
},
"status": "error",
"error": error_msg
}
if error_code:
response["error_code"] = error_code
return response
def make_api_request(url: str, params: Dict[str, Any], timeout: int = 15, max_retries: int = 3) -> requests.Response:
"""Make API request with retry logic and rate limiting."""
for attempt in range(max_retries):
try:
time.sleep(0.1 * attempt)
response = requests.get(url, params=params, timeout=timeout)
if response.status_code == 429:
wait_time = 2 ** attempt
logger.warning(f"Rate limited, waiting {wait_time}s before retry {attempt + 1}")
time.sleep(wait_time)
continue
return response
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
logger.warning(f"Request attempt {attempt + 1} failed: {e}")
raise requests.exceptions.RequestException("Max retries exceeded")
def format_json_output(obj: Any) -> str:
"""
Formats a Python object as a pretty-printed JSON string.
Args:
obj: The Python object to format.
Returns:
A JSON string with an indent of 2 and UTF-8 characters preserved.
"""
return json.dumps(obj, indent=2, ensure_ascii=False) |