0
# Exception Handling
1
2
Custom exception classes for specific error conditions encountered during version discovery and file operations. These exceptions provide structured error handling and enable applications to respond appropriately to different failure scenarios.
3
4
## Capabilities
5
6
### API and Authentication Errors
7
8
Exception classes for handling API-related errors and authentication failures across different platforms.
9
10
```python { .api }
11
class ApiCredentialsError(Exception):
12
"""
13
Raised when there's an API error related to credentials or authentication.
14
15
This exception occurs when:
16
- API tokens are invalid or expired
17
- Rate limits are exceeded due to unauthenticated requests
18
- Private repositories require authentication
19
- API endpoints require elevated permissions
20
21
Common scenarios:
22
- GitHub API rate limiting without token
23
- GitLab private project access
24
- Enterprise instance authentication failures
25
"""
26
27
def __init__(self, message: str, platform: str = None):
28
"""
29
Initialize API credentials error.
30
31
Parameters:
32
- message: Descriptive error message
33
- platform: Platform where error occurred (github, gitlab, etc.)
34
"""
35
```
36
37
### Project and Repository Errors
38
39
Exception for handling cases where specified projects or repositories cannot be found or accessed.
40
41
```python { .api }
42
class BadProjectError(Exception):
43
"""
44
Raised when no such project exists or cannot be accessed.
45
46
This exception occurs when:
47
- Repository does not exist on specified platform
48
- Project has been deleted or made private
49
- Invalid repository identifier format
50
- Platform does not host the specified project
51
- Network issues prevent project access
52
53
Common scenarios:
54
- Typos in repository names
55
- Projects moved between platforms
56
- Private repositories without access
57
- Deleted or archived projects
58
"""
59
60
def __init__(self, message: str, repo: str = None):
61
"""
62
Initialize bad project error.
63
64
Parameters:
65
- message: Descriptive error message
66
- repo: Repository identifier that caused the error
67
"""
68
```
69
70
### Security and File Operation Errors
71
72
Exception for security-related errors during file operations, particularly archive extraction.
73
74
```python { .api }
75
class TarPathTraversalException(Exception):
76
"""
77
Custom exception for path traversal attempts during tar extraction.
78
79
This exception is raised when archive extraction detects potentially
80
malicious archive contents that attempt to write files outside the
81
intended extraction directory.
82
83
Security concerns addressed:
84
- Path traversal attacks using "../" sequences
85
- Absolute path entries in archives
86
- Symbolic link attacks
87
- Archive bombs (deeply nested directories)
88
89
Prevention measures:
90
- All paths are validated before extraction
91
- Relative paths are enforced
92
- Symbolic links are restricted or resolved safely
93
"""
94
95
def __init__(self, message: str, path: str = None):
96
"""
97
Initialize path traversal exception.
98
99
Parameters:
100
- message: Descriptive error message
101
- path: Problematic path that triggered the security check
102
"""
103
```
104
105
## Usage Examples
106
107
### API Credentials Error Handling
108
109
```python
110
from lastversion import latest
111
from lastversion.exceptions import ApiCredentialsError
112
113
def get_version_with_auth_handling(repo):
114
"""Get version with proper authentication error handling."""
115
try:
116
return latest(repo)
117
except ApiCredentialsError as e:
118
print(f"Authentication required for {repo}")
119
print("To resolve:")
120
print("1. Set GITHUB_TOKEN environment variable")
121
print("2. Use personal access token for private repos")
122
print(f"Error details: {e}")
123
return None
124
125
# Example usage
126
version = get_version_with_auth_handling("private-org/private-repo")
127
if version:
128
print(f"Version: {version}")
129
else:
130
print("Could not retrieve version due to authentication issues")
131
```
132
133
### Project Not Found Handling
134
135
```python
136
from lastversion import latest
137
from lastversion.exceptions import BadProjectError
138
139
def find_project_across_platforms(project_name):
140
"""Try to find project across multiple platforms."""
141
platforms = ['github', 'gitlab', 'pip', 'sf']
142
143
for platform in platforms:
144
try:
145
version = latest(project_name, at=platform)
146
if version:
147
print(f"Found {project_name} on {platform}: {version}")
148
return version, platform
149
except BadProjectError:
150
print(f"{project_name} not found on {platform}")
151
continue
152
except Exception as e:
153
print(f"Error checking {platform}: {e}")
154
continue
155
156
raise BadProjectError(f"Project {project_name} not found on any platform")
157
158
# Example usage
159
try:
160
version, platform = find_project_across_platforms("some-project")
161
print(f"Successfully found project on {platform}")
162
except BadProjectError as e:
163
print(f"Project search failed: {e}")
164
```
165
166
### Safe Archive Extraction
167
168
```python
169
from lastversion.utils import extract_file
170
from lastversion.exceptions import TarPathTraversalException
171
import os
172
173
def safe_extract_with_error_handling(url, extract_dir):
174
"""Safely extract archive with comprehensive error handling."""
175
try:
176
# Ensure extraction directory exists
177
os.makedirs(extract_dir, exist_ok=True)
178
179
# Attempt extraction
180
result = extract_file(url, extract_dir)
181
print(f"Successfully extracted to {extract_dir}")
182
return result
183
184
except TarPathTraversalException as e:
185
print(f"Security error: Archive contains unsafe paths")
186
print(f"Blocked path: {e}")
187
print("This archive may be malicious and was not extracted")
188
return None
189
190
except Exception as e:
191
print(f"Extraction failed: {e}")
192
return None
193
194
# Example usage
195
url = "https://example.com/suspicious-archive.tar.gz"
196
result = safe_extract_with_error_handling(url, "/tmp/safe-extract")
197
```
198
199
### Comprehensive Error Handling
200
201
```python
202
from lastversion import latest, has_update
203
from lastversion.exceptions import (
204
ApiCredentialsError,
205
BadProjectError,
206
TarPathTraversalException
207
)
208
from packaging.version import InvalidVersion
209
210
def robust_version_check(repo, current_version=None):
211
"""Comprehensive version checking with full error handling."""
212
try:
213
# Get latest version
214
latest_version = latest(repo)
215
216
if current_version:
217
# Check for updates
218
update = has_update(repo, current_version)
219
return {
220
'latest': latest_version,
221
'current': current_version,
222
'update_available': bool(update),
223
'newer_version': update if update else None,
224
'status': 'success'
225
}
226
else:
227
return {
228
'latest': latest_version,
229
'status': 'success'
230
}
231
232
except ApiCredentialsError as e:
233
return {
234
'status': 'auth_error',
235
'error': str(e),
236
'suggestion': 'Set appropriate API token environment variable'
237
}
238
239
except BadProjectError as e:
240
return {
241
'status': 'not_found',
242
'error': str(e),
243
'suggestion': 'Check repository name and platform'
244
}
245
246
except InvalidVersion as e:
247
return {
248
'status': 'version_error',
249
'error': f"Invalid version format: {e}",
250
'suggestion': 'Check version string format'
251
}
252
253
except Exception as e:
254
return {
255
'status': 'unknown_error',
256
'error': str(e),
257
'suggestion': 'Check network connectivity and repository access'
258
}
259
260
# Example usage
261
result = robust_version_check("kubernetes/kubernetes", "1.28.0")
262
263
if result['status'] == 'success':
264
print(f"Latest: {result['latest']}")
265
if result.get('update_available'):
266
print(f"Update available: {result['current']} → {result['newer_version']}")
267
else:
268
print(f"Error ({result['status']}): {result['error']}")
269
print(f"Suggestion: {result['suggestion']}")
270
```
271
272
### Exception Chaining and Context
273
274
```python
275
from lastversion import latest
276
from lastversion.exceptions import BadProjectError, ApiCredentialsError
277
278
class VersionDiscoveryError(Exception):
279
"""High-level exception for version discovery failures."""
280
pass
281
282
def enterprise_version_check(internal_repo, fallback_repo=None):
283
"""Check enterprise repository with fallback handling."""
284
try:
285
# Try internal/enterprise repository first
286
version = latest(internal_repo, at='github')
287
return version, 'enterprise'
288
289
except ApiCredentialsError as e:
290
print(f"Enterprise auth failed: {e}")
291
292
if fallback_repo:
293
try:
294
# Fallback to public repository
295
version = latest(fallback_repo)
296
return version, 'public_fallback'
297
except BadProjectError as fallback_error:
298
# Chain exceptions to preserve error context
299
raise VersionDiscoveryError(
300
f"Both enterprise and fallback failed"
301
) from fallback_error
302
else:
303
raise VersionDiscoveryError(
304
"Enterprise access failed and no fallback configured"
305
) from e
306
307
except BadProjectError as e:
308
raise VersionDiscoveryError(
309
f"Enterprise repository not found: {internal_repo}"
310
) from e
311
312
# Example usage with exception chaining
313
try:
314
version, source = enterprise_version_check(
315
"internal/secret-project",
316
"public/open-project"
317
)
318
print(f"Version {version} from {source}")
319
320
except VersionDiscoveryError as e:
321
print(f"Version discovery failed: {e}")
322
# Access original exception via __cause__
323
if e.__cause__:
324
print(f"Root cause: {e.__cause__}")
325
```
326
327
### Exception-Based Control Flow
328
329
```python
330
from lastversion import latest
331
from lastversion.exceptions import BadProjectError, ApiCredentialsError
332
333
def get_version_with_retries(repo, max_retries=3):
334
"""Get version with retry logic based on exception types."""
335
import time
336
337
for attempt in range(max_retries):
338
try:
339
return latest(repo)
340
341
except ApiCredentialsError:
342
# Don't retry auth errors
343
raise
344
345
except BadProjectError:
346
# Don't retry not found errors
347
raise
348
349
except Exception as e:
350
# Retry network/temporary errors
351
if attempt < max_retries - 1:
352
wait_time = 2 ** attempt # Exponential backoff
353
print(f"Attempt {attempt + 1} failed: {e}")
354
print(f"Retrying in {wait_time} seconds...")
355
time.sleep(wait_time)
356
else:
357
print(f"All {max_retries} attempts failed")
358
raise
359
360
# Example usage
361
try:
362
version = get_version_with_retries("kubernetes/kubernetes")
363
print(f"Retrieved version: {version}")
364
except (BadProjectError, ApiCredentialsError) as e:
365
print(f"Permanent error: {e}")
366
except Exception as e:
367
print(f"Failed after retries: {e}")
368
```