-
-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy patherror_handler.py
More file actions
144 lines (103 loc) · 3.63 KB
/
Copy patherror_handler.py
File metadata and controls
144 lines (103 loc) · 3.63 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
Error handling utilities and decorators for robust operation.
"""
import functools
from typing import Callable, Any, Optional
import httpx
from github import GithubException
from forklet.infrastructure.logger import logger
####
## DOWNLOAD ERROR MODEL
#####
class DownloadError(Exception):
"""Base exception for download-related errors."""
def __init__(self, message: str, original_error: Optional[Exception] = None):
super().__init__(message)
self.original_error = original_error
self.message = message
def __str__(self) -> str:
if self.original_error:
return f"{self.message} (Original: {self.original_error})"
return self.message
####
## RATE LIMITER ERROR
#####
class RateLimitError(DownloadError):
"""Exception raised when rate limits are exceeded."""
pass
####
## AUTHENTICATION ERROR
#####
class AuthenticationError(DownloadError):
"""Exception raised for authentication failures."""
pass
####
## REPO NOT FOUND ERROR
#####
class RepositoryNotFoundError(DownloadError):
"""Exception raised when repository is not found."""
pass
####
## ERROR HANDLER UTILITIES
#####
def handle_api_error(func: Callable) -> Callable:
"""
Decorator to handle API errors and convert to appropriate exceptions.
Args:
func: Function to decorate
Returns:
Decorated function
"""
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
try:
return func(*args, **kwargs)
# Cse of GH Exceptions
except GithubException as e:
if e.status == 403 and "rate limit" in str(e).lower():
raise RateLimitError("GitHub API rate limit exceeded", e) from e
elif e.status == 401 or e.status == 403:
raise AuthenticationError("Authentication failed", e) from e
elif e.status == 404:
raise RepositoryNotFoundError("Repository not found", e) from e
else:
raise DownloadError(f"GitHub API error: {e}", e) from e
# Request Exceptions
except httpx.RequestError as e:
if "429" in str(e) or "rate limit" in str(e).lower():
raise RateLimitError("Rate limit exceeded", e) from e
else:
raise DownloadError(f"Network error: {e}", e) from e
except Exception as e:
raise DownloadError(f"Unexpected error: {e}", e) from e
return wrapper
def retry_on_error(max_retries: int = 3) -> Callable:
"""
Decorator to retry operations on specific errors.
Args:
max_retries: Maximum number of retry attempts
Returns:
Decorator function
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
last_exception = None
for attempt in range(max_retries + 1):
try:
return func(*args, **kwargs)
except (RateLimitError, httpx.RequestError, ConnectionError) as e:
last_exception = e
if attempt < max_retries:
logger.warning(
f"Retry {attempt + 1}/{max_retries} after error: {e}"
)
continue
raise
except Exception as e:
# Don't retry on other errors
logger.error(f"Non-retryable error: {e}")
raise
raise last_exception or Exception("All retry attempts failed")
return wrapper
return decorator