147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
import json
|
|
import logging
|
|
import traceback
|
|
from typing import Any, Dict
|
|
|
|
import httpx
|
|
from httpx import ConnectError, HTTPStatusError, ReadTimeout
|
|
|
|
logger = logging.getLogger("mealie-mcp")
|
|
|
|
|
|
class MealieApiError(Exception):
|
|
"""Custom exception for Mealie API errors with status code and response details."""
|
|
|
|
def __init__(self, status_code: int, message: str, response_text: str = None):
|
|
self.status_code = status_code
|
|
self.message = message
|
|
self.response_text = response_text
|
|
super().__init__(f"{message} (Status Code: {status_code})")
|
|
|
|
|
|
class MealieClient:
|
|
|
|
def __init__(self, base_url: str, api_key: str):
|
|
if not base_url:
|
|
raise ValueError("Base URL cannot be empty")
|
|
if not api_key:
|
|
raise ValueError("API key cannot be empty")
|
|
|
|
logger.debug({"message": "Initializing MealieClient", "base_url": base_url})
|
|
try:
|
|
self._client = httpx.Client(
|
|
base_url=base_url,
|
|
headers={
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json",
|
|
},
|
|
timeout=30.0, # Set a reasonable timeout for requests
|
|
)
|
|
# Test connection
|
|
logger.debug({"message": "Testing connection to Mealie API"})
|
|
self._client.get("/api/app/about")
|
|
logger.info({"message": "Successfully connected to Mealie API"})
|
|
except ConnectError as e:
|
|
error_msg = f"Failed to connect to Mealie API at {base_url}: {str(e)}"
|
|
logger.error({"message": error_msg})
|
|
logger.debug(
|
|
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
|
)
|
|
raise ConnectionError(error_msg) from e
|
|
except Exception as e:
|
|
error_msg = f"Error initializing Mealie client: {str(e)}"
|
|
logger.error({"message": error_msg})
|
|
logger.debug(
|
|
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
|
)
|
|
raise
|
|
|
|
def _handle_request(self, method: str, url: str, **kwargs) -> Dict[str, Any] | str:
|
|
"""Common request handler with error handling for all API calls."""
|
|
try:
|
|
logger.debug(
|
|
{
|
|
"message": "Making API request",
|
|
"method": method,
|
|
"url": url,
|
|
"body": kwargs.get("json"),
|
|
}
|
|
)
|
|
|
|
if "params" in kwargs:
|
|
logger.debug(
|
|
{"message": "Request parameters", "params": kwargs["params"]}
|
|
)
|
|
if "json" in kwargs:
|
|
logger.debug({"message": "Request payload", "payload": kwargs["json"]})
|
|
|
|
response = self._client.request(method, url, **kwargs)
|
|
response.raise_for_status() # Raise an exception for 4XX/5XX responses
|
|
|
|
logger.debug(
|
|
{"message": "Request successful", "status_code": response.status_code}
|
|
)
|
|
# Log the response content at debug level
|
|
try:
|
|
response_data = response.json()
|
|
logger.debug({"message": "Response content", "data": response_data})
|
|
return response_data
|
|
except json.JSONDecodeError:
|
|
logger.debug(
|
|
{"message": "Response content (non-JSON)", "content": response.text}
|
|
)
|
|
return response.text
|
|
|
|
except HTTPStatusError as e:
|
|
status_code = e.response.status_code
|
|
error_detail = f"HTTP Error {status_code}"
|
|
|
|
# Try to parse error details from response
|
|
try:
|
|
error_detail = e.response.json()
|
|
except Exception:
|
|
error_detail = e.response.text
|
|
|
|
error_msg = f"API error for {method} {url}: {error_detail}"
|
|
logger.error(
|
|
{
|
|
"message": "API request failed",
|
|
"method": method,
|
|
"url": url,
|
|
"status_code": status_code,
|
|
"error_detail": error_detail,
|
|
}
|
|
)
|
|
logger.debug(
|
|
{"message": "Failed Request body", "content": e.request.content}
|
|
)
|
|
raise MealieApiError(status_code, error_msg, e.response.text) from e
|
|
|
|
except ReadTimeout:
|
|
error_msg = f"Request timeout for {method} {url}"
|
|
logger.error({"message": error_msg, "method": method, "url": url})
|
|
logger.debug(
|
|
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
|
)
|
|
raise TimeoutError(error_msg)
|
|
|
|
except ConnectError as e:
|
|
error_msg = f"Connection error for {method} {url}: {str(e)}"
|
|
logger.error(
|
|
{"message": error_msg, "method": method, "url": url, "error": str(e)}
|
|
)
|
|
logger.debug(
|
|
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
|
)
|
|
raise ConnectionError(error_msg) from e
|
|
|
|
except Exception as e:
|
|
error_msg = f"Unexpected error for {method} {url}: {str(e)}"
|
|
logger.error(
|
|
{"message": error_msg, "method": method, "url": url, "error": str(e)}
|
|
)
|
|
logger.debug(
|
|
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
|
)
|
|
raise
|