Skip to content

Commit b7e24ee

Browse files
authored
feat(error-handling): enhance error handling with detailed logging and flexible decorator (#69)
feat(error-handling): enhance error handling with detailed logging and flexible decorator (#69)
2 parents e9ce726 + 7288177 commit b7e24ee

5 files changed

Lines changed: 48 additions & 51 deletions

File tree

forklet/core/orchestrator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .progress_tracker import ProgressTracker
2525
from .state_controller import StateController
2626

27+
from forklet.infrastructure import RateLimitInfo
2728
from forklet.infrastructure.logger import logger
2829

2930

forklet/infrastructure/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from .error_handler import (
2-
DownloadError, RateLimitError, RateLimitError,
2+
DownloadError, RateLimitError,
33
AuthenticationError, RepositoryNotFoundError,
44
handle_api_error, retry_on_error
55
)
66

7-
from .rate_limiter import RateLimiter
7+
from .rate_limiter import RateLimiter, RateLimitInfo
88
from .retry_manager import RetryManager
9+
from .cache_manager import CacheManager, CacheEntry
910

1011
__all__ = [
11-
DownloadError, RateLimitError, RateLimitError,
12+
DownloadError, RateLimitError,
1213
AuthenticationError, RepositoryNotFoundError,
1314
handle_api_error, retry_on_error, RateLimiter,
14-
RetryManager
15+
RetryManager, RateLimitInfo, CacheManager, CacheEntry
1516
]

forklet/infrastructure/error_handler.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,17 @@
1111
from forklet.infrastructure.logger import logger
1212

1313

14-
15-
1614
####
1715
## DOWNLOAD ERROR MODEL
1816
#####
1917
class DownloadError(Exception):
2018
"""Base exception for download-related errors."""
21-
22-
def __init__(
23-
self,
24-
message: str,
25-
original_error: Optional[Exception] = None
26-
):
19+
20+
def __init__(self, message: str, original_error: Optional[Exception] = None):
2721
super().__init__(message)
2822
self.original_error = original_error
2923
self.message = message
30-
24+
3125
def __str__(self) -> str:
3226
if self.original_error:
3327
return f"{self.message} (Original: {self.original_error})"
@@ -39,6 +33,7 @@ def __str__(self) -> str:
3933
#####
4034
class RateLimitError(DownloadError):
4135
"""Exception raised when rate limits are exceeded."""
36+
4237
pass
4338

4439

@@ -47,6 +42,7 @@ class RateLimitError(DownloadError):
4742
#####
4843
class AuthenticationError(DownloadError):
4944
"""Exception raised for authentication failures."""
45+
5046
pass
5147

5248

@@ -55,20 +51,20 @@ class AuthenticationError(DownloadError):
5551
#####
5652
class RepositoryNotFoundError(DownloadError):
5753
"""Exception raised when repository is not found."""
58-
pass
5954

55+
pass
6056

6157

6258
####
63-
## RROR HANDLER UTILITIES
59+
## ERROR HANDLER UTILITIES
6460
#####
6561
def handle_api_error(func: Callable) -> Callable:
6662
"""
6763
Decorator to handle API errors and convert to appropriate exceptions.
68-
64+
6965
Args:
7066
func: Function to decorate
71-
67+
7268
Returns:
7369
Decorated function
7470
"""
@@ -81,8 +77,7 @@ def wrapper(*args, **kwargs) -> Any:
8177

8278
# Cse of GH Exceptions
8379
except GithubException as e:
84-
85-
if e.status == 403 and 'rate limit' in str(e).lower():
80+
if e.status == 403 and "rate limit" in str(e).lower():
8681
raise RateLimitError("GitHub API rate limit exceeded", e) from e
8782

8883
elif e.status == 401 or e.status == 403:
@@ -96,45 +91,39 @@ def wrapper(*args, **kwargs) -> Any:
9691

9792
# Request Exceptions
9893
except httpx.RequestError as e:
99-
100-
if '429' in str(e) or 'rate limit' in str(e).lower():
94+
if "429" in str(e) or "rate limit" in str(e).lower():
10195
raise RateLimitError("Rate limit exceeded", e) from e
10296

10397
else:
10498
raise DownloadError(f"Network error: {e}", e) from e
10599

106100
except Exception as e:
107101
raise DownloadError(f"Unexpected error: {e}", e) from e
108-
102+
109103
return wrapper
110104

111105

112106
def retry_on_error(max_retries: int = 3) -> Callable:
113107
"""
114108
Decorator to retry operations on specific errors.
115-
109+
116110
Args:
117111
max_retries: Maximum number of retry attempts
118-
112+
119113
Returns:
120-
Decorated function
114+
Decorator function
121115
"""
122116

123117
def decorator(func: Callable) -> Callable:
124-
118+
125119
@functools.wraps(func)
126120
def wrapper(*args, **kwargs) -> Any:
127121
last_exception = None
128-
122+
129123
for attempt in range(max_retries + 1):
130124
try:
131125
return func(*args, **kwargs)
132-
except (
133-
RateLimitError,
134-
httpx.RequestError,
135-
ConnectionError
136-
) as e:
137-
126+
except (RateLimitError, httpx.RequestError, ConnectionError) as e:
138127
last_exception = e
139128
if attempt < max_retries:
140129
logger.warning(
@@ -147,7 +136,9 @@ def wrapper(*args, **kwargs) -> Any:
147136
# Don't retry on other errors
148137
logger.error(f"Non-retryable error: {e}")
149138
raise
150-
139+
151140
raise last_exception or Exception("All retry attempts failed")
141+
152142
return wrapper
143+
153144
return decorator

forklet/models/download.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from datetime import datetime
1212
from enum import Enum
1313
from pathlib import Path
14-
from typing import Dict, List, Optional, Set
14+
from typing import Dict, List, Optional, Set, Any
1515

1616
from .github import RepositoryInfo, GitReference
1717

forklet/services/github_api.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22
Service for interacting with GitHub API with rate limiting and error handling.
33
"""
44

5-
from typing import List, Optional, Dict, Any, AsyncIterator
6-
5+
from typing import List, Optional, Dict, Any, AsyncIterator, Callable
6+
import datetime
77
import asyncio
88
import httpx
99
from github import Github, GithubException
1010
# from github.Repository import Repository as GithubRepository
1111

12-
from ..infrastructure.rate_limiter import RateLimiter
13-
from ..infrastructure.retry_manager import RetryManager
14-
from ..infrastructure.error_handler import (
12+
# from ..infrastructure.rate_limiter import RateLimiter
13+
# from ..infrastructure.retry_manager import RetryManager
14+
from ..infrastructure import (
1515
handle_api_error,
1616
RateLimitError,
1717
RepositoryNotFoundError,
1818
DownloadError,
19+
RateLimiter,
20+
RetryManager,
21+
CacheManager,
22+
RateLimitInfo
1923
)
20-
from ..infrastructure.cache_manager import CacheManager
24+
# from ..infrastructure.cache_manager import CacheManager
2125
from ..models import RepositoryInfo, GitReference, RepositoryType, GitHubFile
2226
from ..models.constants import USER_AGENT
2327

@@ -75,19 +79,19 @@ def _on_rate_limit_update(self, rate_limit_info: RateLimitInfo) -> None:
7579
# Configure HTTP client
7680
headers = {"Accept": "application/vnd.github.v3+json", "User-Agent": USER_AGENT}
7781

78-
if auth_token:
79-
headers["Authorization"] = f"token {auth_token}"
82+
if self.auth_token:
83+
headers["Authorization"] = f"token {self.auth_token}"
8084

8185
self.http_client = httpx.AsyncClient(
82-
headers=headers, timeout=httpx.Timeout(timeout)
86+
headers=headers, timeout=httpx.Timeout(self.timeout)
8387
)
8488

8589
# Sync client for PyGithub (used only for metadata)
8690
self.github_client = (
8791
Github(
88-
auth_token, retry=self.retry_manager.max_retries, user_agent=USER_AGENT
92+
self.auth_token, retry=self.retry_manager.max_retries, user_agent=USER_AGENT
8993
)
90-
if auth_token
94+
if self.auth_token
9195
else Github(retry=self.retry_manager.max_retries, user_agent=USER_AGENT)
9296
)
9397

@@ -129,19 +133,19 @@ async def update_rate_limit_info(self, headers: Dict[str, str]) -> None:
129133
# Configure HTTP client
130134
headers = {"Accept": "application/vnd.github.v3+json", "User-Agent": USER_AGENT}
131135

132-
if auth_token:
133-
headers["Authorization"] = f"token {auth_token}"
136+
if self.auth_token:
137+
headers["Authorization"] = f"token {self.auth_token}"
134138

135139
self.http_client = httpx.AsyncClient(
136-
headers=headers, timeout=httpx.Timeout(timeout)
140+
headers=headers, timeout=httpx.Timeout(self.timeout)
137141
)
138142

139143
# Sync client for PyGithub (used only for metadata)
140144
self.github_client = (
141145
Github(
142-
auth_token, retry=self.retry_manager.max_retries, user_agent=USER_AGENT
146+
self.auth_token, retry=self.retry_manager.max_retries, user_agent=USER_AGENT
143147
)
144-
if auth_token
148+
if self.auth_token
145149
else Github(retry=self.retry_manager.max_retries, user_agent=USER_AGENT)
146150
)
147151

0 commit comments

Comments
 (0)