pharmacy-mcp / utils.py
Chris McMaster
Initial commit
a04c9e9
raw
history blame
3.84 kB
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."""
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) -> Dict[str, Any]:
"""Create standardized error response."""
return {
"data": None,
"metadata": {
"source": source,
"timestamp": datetime.now().isoformat(),
"version": "1.1.0"
},
"status": "error",
"error": error_msg
}
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)