Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions mem0/client/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
NetworkError,
create_exception_from_response,
)
from functools import wraps

logger = logging.getLogger(__name__)


class APIError(Exception):
"""Exception raised for errors in the API.

Deprecated: Use specific exception classes from mem0.exceptions instead.
This class is maintained for backward compatibility.
"""
Expand All @@ -22,23 +23,22 @@ class APIError(Exception):

def api_error_handler(func):
"""Decorator to handle API errors consistently.

This decorator catches HTTP and request errors and converts them to
appropriate structured exception classes with detailed error information.

The decorator analyzes HTTP status codes and response content to create
the most specific exception type with helpful error messages, suggestions,
and debug information.
"""
from functools import wraps

@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error occurred: {e}")

# Extract error details from response
response_text = ""
error_details = {}
Expand All @@ -47,7 +47,7 @@ def wrapper(*args, **kwargs):
"url": str(e.request.url),
"method": e.request.method,
}

try:
response_text = e.response.text
# Try to parse JSON response for additional error details
Expand All @@ -59,7 +59,7 @@ def wrapper(*args, **kwargs):
except (json.JSONDecodeError, AttributeError):
# Fallback to plain text response
pass

# Add rate limit information if available
if e.response.status_code == 429:
retry_after = e.response.headers.get("Retry-After")
Expand All @@ -68,48 +68,60 @@ def wrapper(*args, **kwargs):
debug_info["retry_after"] = int(retry_after)
except ValueError:
pass

# Add rate limit headers if available
for header in ["X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"]:
value = e.response.headers.get(header)
if value:
debug_info[header.lower().replace("-", "_")] = value

# Create specific exception based on status code
exception = create_exception_from_response(
status_code=e.response.status_code,
response_text=response_text,
details=error_details,
debug_info=debug_info,
)

raise exception

except httpx.RequestError as e:
logger.error(f"Request error occurred: {e}")

# Determine the appropriate exception type based on error type

orig_err = str(e)
# Type checking for exception subtypes is unavoidable here but make as efficient as possible
if isinstance(e, httpx.TimeoutException):
raise NetworkError(
message=f"Request timed out: {str(e)}",
message=f"Request timed out: {orig_err}",
error_code="NET_TIMEOUT",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "timeout", "original_error": str(e)},
debug_info={
"error_type": "timeout",
"original_error": orig_err,
},
)
elif isinstance(e, httpx.ConnectError):
raise NetworkError(
message=f"Connection failed: {str(e)}",
message=f"Connection failed: {orig_err}",
error_code="NET_CONNECT",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "connection", "original_error": str(e)},
debug_info={
"error_type": "connection",
"original_error": orig_err,
},
)
else:
# Generic network error for other request errors
raise NetworkError(
message=f"Network request failed: {str(e)}",
message=f"Network request failed: {orig_err}",
error_code="NET_GENERIC",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "request", "original_error": str(e)},
debug_info={
"error_type": "request",
"original_error": orig_err,
},
)

return wrapper