0
# Error Handling and Exceptions
1
2
Safety CLI provides a comprehensive error handling system with specific exception types for different failure scenarios. This enables robust error handling in automated environments and clear feedback for developers.
3
4
## Core Exception Classes { .api }
5
6
**Import Statements:**
7
8
```python
9
from safety.errors import (
10
# Base exception classes
11
SafetyError, SafetyException,
12
13
# Database and connectivity errors
14
DatabaseFetchError, DatabaseFileNotFoundError, MalformedDatabase,
15
NetworkConnectionError, RequestTimeoutError, ServerError, TooManyRequestsError,
16
17
# Authentication and validation errors
18
InvalidCredentialError, NotVerifiedEmailError, InvalidRequirementError,
19
InvalidProvidedReportError
20
)
21
from safety.constants import (
22
EXIT_CODE_FAILURE, EXIT_CODE_VULNERABILITIES_FOUND,
23
EXIT_CODE_INVALID_AUTH_CREDENTIAL, EXIT_CODE_INVALID_REQUIREMENT,
24
EXIT_CODE_MALFORMED_DB, EXIT_CODE_TOO_MANY_REQUESTS,
25
EXIT_CODE_UNABLE_TO_FETCH_VULNERABILITY_DB,
26
EXIT_CODE_UNABLE_TO_LOAD_LOCAL_VULNERABILITY_DB,
27
EXIT_CODE_EMAIL_NOT_VERIFIED, EXIT_CODE_INVALID_PROVIDED_REPORT
28
)
29
```
30
31
### SafetyException { .api }
32
33
**Description**: Base exception class for all Safety CLI errors with formatted messaging.
34
35
```python
36
class SafetyException(Exception):
37
"""Base exception for Safety CLI errors with template messaging."""
38
39
def __init__(self,
40
message: str = "Unhandled exception happened: {info}",
41
info: str = "") -> None:
42
"""
43
Initialize Safety exception with formatted message.
44
45
Args:
46
message (str): Error message template with {info} placeholder
47
info (str): Additional information for message template
48
"""
49
50
def get_exit_code(self) -> int:
51
"""
52
Get the exit code associated with this exception.
53
54
Returns:
55
int: Exit code (default: EXIT_CODE_FAILURE = 1)
56
"""
57
```
58
59
**Example Usage:**
60
61
```python
62
from safety.errors import SafetyException
63
64
# Basic exception with default message
65
raise SafetyException(info="Database connection failed")
66
67
# Custom message template
68
raise SafetyException(
69
message="Scan failed for project {info}",
70
info="my-project-v1.0"
71
)
72
73
# Handle exception and get exit code
74
try:
75
# Safety operation
76
pass
77
except SafetyException as e:
78
print(f"Error: {e.message}")
79
exit(e.get_exit_code())
80
```
81
82
### SafetyError { .api }
83
84
**Description**: Generic Safety CLI error with optional error codes.
85
86
```python
87
class SafetyError(Exception):
88
"""Generic Safety CLI error with error code support."""
89
90
def __init__(self,
91
message: str = "Unhandled Safety generic error",
92
error_code: Optional[int] = None) -> None:
93
"""
94
Initialize Safety error.
95
96
Args:
97
message (str): Error description
98
error_code (Optional[int]): Numeric error code for categorization
99
"""
100
101
message: str # Error message
102
error_code: Optional[int] # Optional error code
103
```
104
105
**Example Usage:**
106
107
```python
108
from safety.errors import SafetyError
109
110
# Generic error
111
raise SafetyError("Failed to process vulnerability data")
112
113
# Error with code
114
raise SafetyError(
115
message="API rate limit exceeded",
116
error_code=429
117
)
118
119
# Handle with error code checking
120
try:
121
# Safety operation
122
pass
123
except SafetyError as e:
124
if e.error_code == 429:
125
print("Rate limited - retrying in 60 seconds")
126
else:
127
print(f"Safety error: {e.message}")
128
```
129
130
## Specific Exception Types
131
132
### Authentication Errors { .api }
133
134
#### InvalidCredentialError { .api }
135
136
**Description**: Raised when authentication credentials are invalid or expired.
137
138
```python
139
class InvalidCredentialError(SafetyError):
140
"""Authentication credential validation error."""
141
142
def get_exit_code(self) -> int:
143
"""Returns EXIT_CODE_INVALID_AUTH_CREDENTIAL (2)"""
144
```
145
146
**Common Scenarios:**
147
- Invalid API key
148
- Expired authentication token
149
- Insufficient permissions for organization
150
- Email not verified
151
152
**Example Usage:**
153
154
```python
155
from safety.errors import InvalidCredentialError
156
from safety.auth.utils import SafetyAuthSession
157
158
try:
159
session = SafetyAuthSession()
160
session.api_key = "invalid-key"
161
response = session.get("/user/profile")
162
except InvalidCredentialError as e:
163
print("Authentication failed - please run 'safety auth login'")
164
exit(2)
165
```
166
167
### Network and Connection Errors { .api }
168
169
#### NetworkConnectionError { .api }
170
171
**Description**: Network connectivity and communication errors.
172
173
```python
174
class NetworkConnectionError(SafetyError):
175
"""Network connectivity error."""
176
177
# Common causes:
178
# - No internet connection
179
# - Proxy configuration issues
180
# - DNS resolution failures
181
# - Firewall blocking connections
182
```
183
184
#### RequestTimeoutError { .api }
185
186
**Description**: Request timeout errors for API calls and downloads.
187
188
```python
189
class RequestTimeoutError(SafetyError):
190
"""Request timeout error."""
191
192
# Triggered when:
193
# - API requests exceed timeout limit
194
# - Database downloads are slow
195
# - Network latency is high
196
```
197
198
#### ServerError { .api }
199
200
**Description**: Server-side errors from Safety platform APIs.
201
202
```python
203
class ServerError(SafetyError):
204
"""Server-side error from Safety platform."""
205
206
# Indicates:
207
# - Safety platform maintenance
208
# - Internal server errors (5xx)
209
# - Service unavailability
210
```
211
212
#### TooManyRequestsError { .api }
213
214
**Description**: API rate limiting errors.
215
216
```python
217
class TooManyRequestsError(SafetyError):
218
"""API rate limiting error."""
219
220
def get_exit_code(self) -> int:
221
"""Returns EXIT_CODE_TOO_MANY_REQUESTS (3)"""
222
```
223
224
**Example Usage:**
225
226
```python
227
from safety.errors import (
228
NetworkConnectionError, RequestTimeoutError,
229
ServerError, TooManyRequestsError
230
)
231
import time
232
233
def retry_with_backoff(operation, max_retries=3):
234
"""Retry operation with exponential backoff for network errors."""
235
236
for attempt in range(max_retries):
237
try:
238
return operation()
239
except TooManyRequestsError:
240
if attempt < max_retries - 1:
241
wait_time = 2 ** attempt * 60 # 60s, 120s, 240s
242
print(f"Rate limited - waiting {wait_time}s before retry")
243
time.sleep(wait_time)
244
else:
245
raise
246
except (NetworkConnectionError, RequestTimeoutError) as e:
247
if attempt < max_retries - 1:
248
print(f"Network error - retrying in 10s: {e}")
249
time.sleep(10)
250
else:
251
raise
252
except ServerError:
253
print("Safety platform unavailable - please try again later")
254
raise
255
```
256
257
### Data and Parsing Errors { .api }
258
259
#### InvalidRequirementError { .api }
260
261
**Description**: Requirement parsing and validation errors.
262
263
```python
264
class InvalidRequirementError(SafetyError):
265
"""Requirement parsing error."""
266
267
def get_exit_code(self) -> int:
268
"""Returns EXIT_CODE_INVALID_REQUIREMENT (4)"""
269
```
270
271
**Common Causes:**
272
- Malformed requirement specifications
273
- Invalid version constraints
274
- Unsupported requirement syntax
275
- Missing package names
276
277
**Example Usage:**
278
279
```python
280
from safety.errors import InvalidRequirementError
281
from safety.models import SafetyRequirement
282
283
def parse_requirement_safely(req_string: str) -> Optional[SafetyRequirement]:
284
"""Parse requirement with error handling."""
285
286
try:
287
return SafetyRequirement(req_string)
288
except InvalidRequirementError as e:
289
print(f"Invalid requirement '{req_string}': {e.message}")
290
return None
291
292
# Usage
293
requirements = [
294
"requests>=2.20.0",
295
"django>=3.0,<4.0",
296
"invalid-requirement-syntax!!!" # This will fail
297
]
298
299
valid_requirements = []
300
for req_str in requirements:
301
req = parse_requirement_safely(req_str)
302
if req:
303
valid_requirements.append(req)
304
```
305
306
## Exit Codes { .api }
307
308
Safety CLI uses specific exit codes to indicate different error conditions:
309
310
```python
311
from safety.constants import (
312
EXIT_CODE_OK, # 0 - Success
313
EXIT_CODE_FAILURE, # 1 - General failure
314
EXIT_CODE_INVALID_AUTH_CREDENTIAL, # 2 - Authentication error
315
EXIT_CODE_TOO_MANY_REQUESTS, # 3 - Rate limiting
316
EXIT_CODE_INVALID_REQUIREMENT, # 4 - Requirement parsing error
317
EXIT_CODE_MALFORMED_DB, # 5 - Database corruption
318
EXIT_CODE_UNABLE_TO_FETCH_VULNERABILITY_DB, # 6 - DB download failure
319
EXIT_CODE_UNABLE_TO_LOAD_LOCAL_VULNERABILITY_DB, # 7 - Local DB error
320
EXIT_CODE_EMAIL_NOT_VERIFIED, # 8 - Email verification required
321
EXIT_CODE_INVALID_PROVIDED_REPORT, # 9 - Invalid report format
322
EXIT_CODE_VULNERABILITIES_FOUND # 64 - Vulnerabilities detected
323
)
324
325
# Exit code mapping
326
EXIT_CODE_DESCRIPTIONS = {
327
0: "Success - no issues found",
328
1: "General failure or error",
329
2: "Invalid authentication credentials",
330
3: "Too many requests - rate limited",
331
4: "Invalid requirement specification",
332
5: "Malformed vulnerability database",
333
6: "Unable to fetch vulnerability database",
334
7: "Unable to load local vulnerability database",
335
8: "Email verification required",
336
9: "Invalid report format provided",
337
64: "Vulnerabilities found in dependencies"
338
}
339
```
340
341
## Error Handling Patterns
342
343
### Exception Hierarchy { .api }
344
345
```python
346
# Exception inheritance hierarchy
347
Exception
348
├── SafetyException (base with exit codes)
349
│ └── [Various specific exceptions inherit exit code behavior]
350
└── SafetyError (generic with error codes)
351
├── InvalidCredentialError
352
├── NetworkConnectionError
353
├── RequestTimeoutError
354
├── ServerError
355
├── TooManyRequestsError
356
└── InvalidRequirementError
357
```
358
359
### Comprehensive Error Handling { .api }
360
361
```python
362
from safety.errors import *
363
import logging
364
365
def handle_safety_operation():
366
"""Example of comprehensive error handling for Safety operations."""
367
368
logger = logging.getLogger(__name__)
369
370
try:
371
# Perform Safety operation
372
result = safety_scan_operation()
373
return result
374
375
except InvalidCredentialError as e:
376
logger.error(f"Authentication failed: {e.message}")
377
print("Please run 'safety auth login' to authenticate")
378
return None
379
380
except TooManyRequestsError as e:
381
logger.warning(f"Rate limited: {e.message}")
382
print("API rate limit exceeded - please try again later")
383
return None
384
385
except NetworkConnectionError as e:
386
logger.error(f"Network error: {e.message}")
387
print("Unable to connect to Safety platform - check network connection")
388
return None
389
390
except InvalidRequirementError as e:
391
logger.error(f"Requirement parsing failed: {e.message}")
392
print("Invalid requirement format in dependency files")
393
return None
394
395
except ServerError as e:
396
logger.error(f"Platform error: {e.message}")
397
print("Safety platform is temporarily unavailable")
398
return None
399
400
except SafetyException as e:
401
logger.error(f"Safety exception: {e.message}")
402
print(f"Safety CLI error: {e.message}")
403
exit(e.get_exit_code())
404
405
except SafetyError as e:
406
logger.error(f"Safety error: {e.message}")
407
if e.error_code:
408
print(f"Error {e.error_code}: {e.message}")
409
else:
410
print(f"Error: {e.message}")
411
return None
412
413
except Exception as e:
414
logger.exception("Unexpected error in Safety operation")
415
print(f"Unexpected error: {e}")
416
exit(EXIT_CODE_FAILURE)
417
```
418
419
### Error Context Management { .api }
420
421
```python
422
from contextlib import contextmanager
423
from safety.errors import SafetyError
424
425
@contextmanager
426
def safety_operation_context(operation_name: str):
427
"""Context manager for Safety operations with error handling."""
428
429
try:
430
print(f"Starting {operation_name}...")
431
yield
432
print(f"✅ {operation_name} completed successfully")
433
434
except SafetyError as e:
435
print(f"❌ {operation_name} failed: {e.message}")
436
if hasattr(e, 'get_exit_code'):
437
exit(e.get_exit_code())
438
else:
439
exit(EXIT_CODE_FAILURE)
440
441
except Exception as e:
442
print(f"❌ {operation_name} failed unexpectedly: {e}")
443
exit(EXIT_CODE_FAILURE)
444
445
# Usage
446
with safety_operation_context("vulnerability scan"):
447
scan_results = perform_vulnerability_scan()
448
449
with safety_operation_context("license check"):
450
license_results = perform_license_check()
451
```
452
453
### Logging Integration { .api }
454
455
```python
456
import logging
457
from safety.errors import SafetyError, SafetyException
458
459
# Configure logging for error tracking
460
logging.basicConfig(
461
level=logging.INFO,
462
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
463
handlers=[
464
logging.FileHandler('safety.log'),
465
logging.StreamHandler()
466
]
467
)
468
469
logger = logging.getLogger('safety.errors')
470
471
def log_and_handle_error(e: Exception, context: str = ""):
472
"""Centralized error logging and handling."""
473
474
if isinstance(e, SafetyException):
475
logger.error(f"Safety exception in {context}: {e.message}")
476
return e.get_exit_code()
477
478
elif isinstance(e, SafetyError):
479
logger.error(f"Safety error in {context}: {e.message} (code: {e.error_code})")
480
return EXIT_CODE_FAILURE
481
482
else:
483
logger.exception(f"Unexpected error in {context}")
484
return EXIT_CODE_FAILURE
485
```
486
487
## CI/CD Error Handling
488
489
### Exit Code Handling in CI/CD { .api }
490
491
```bash
492
#!/bin/bash
493
# CI/CD script with Safety error handling
494
495
set -e # Exit on any error
496
497
safety scan
498
SAFETY_EXIT_CODE=$?
499
500
case $SAFETY_EXIT_CODE in
501
0)
502
echo "✅ No security issues found"
503
;;
504
64)
505
echo "🚨 Vulnerabilities found - failing build"
506
exit 1
507
;;
508
2)
509
echo "❌ Authentication failed - check SAFETY_API_KEY"
510
exit 1
511
;;
512
3)
513
echo "⏳ Rate limited - retrying after delay"
514
sleep 60
515
safety scan
516
;;
517
*)
518
echo "❌ Safety scan failed with exit code $SAFETY_EXIT_CODE"
519
exit 1
520
;;
521
esac
522
```
523
524
### GitHub Actions Error Handling { .api }
525
526
```yaml
527
# .github/workflows/security-scan.yml
528
name: Security Scan
529
530
on: [push, pull_request]
531
532
jobs:
533
security:
534
runs-on: ubuntu-latest
535
steps:
536
- uses: actions/checkout@v3
537
538
- name: Setup Python
539
uses: actions/setup-python@v4
540
with:
541
python-version: '3.11'
542
543
- name: Install Safety
544
run: pip install safety
545
546
- name: Run Security Scan
547
env:
548
SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}
549
run: |
550
set -e
551
safety scan --output json --save-as json:security-report.json || EXIT_CODE=$?
552
553
case ${EXIT_CODE:-0} in
554
0)
555
echo "✅ No security issues found"
556
;;
557
64)
558
echo "🚨 Security vulnerabilities detected"
559
echo "::error::Security vulnerabilities found - see security-report.json"
560
exit 1
561
;;
562
2)
563
echo "::error::Authentication failed - check SAFETY_API_KEY secret"
564
exit 1
565
;;
566
*)
567
echo "::error::Safety scan failed with exit code ${EXIT_CODE:-0}"
568
exit 1
569
;;
570
esac
571
572
- name: Upload Security Report
573
if: always()
574
uses: actions/upload-artifact@v3
575
with:
576
name: security-report
577
path: security-report.json
578
```
579
580
### Additional Error Classes { .api }
581
582
#### MalformedDatabase { .api }
583
584
**Description**: Vulnerability database corruption or format errors.
585
586
```python
587
class MalformedDatabase(SafetyError):
588
"""Malformed vulnerability database error."""
589
590
def get_exit_code(self) -> int:
591
"""Returns EXIT_CODE_MALFORMED_DB (69)"""
592
```
593
594
#### DatabaseFileNotFoundError { .api }
595
596
**Description**: Local vulnerability database file missing or inaccessible.
597
598
```python
599
class DatabaseFileNotFoundError(DatabaseFetchError):
600
"""Local vulnerability database file not found."""
601
```
602
603
#### NotVerifiedEmailError { .api }
604
605
**Description**: Account email verification required for API access.
606
607
```python
608
class NotVerifiedEmailError(SafetyError):
609
"""Email verification required error."""
610
611
def get_exit_code(self) -> int:
612
"""Returns EXIT_CODE_EMAIL_NOT_VERIFIED (72)"""
613
```
614
615
#### InvalidProvidedReportError { .api }
616
617
**Description**: Invalid report format or content provided to Safety.
618
619
```python
620
class InvalidProvidedReportError(SafetyError):
621
"""Invalid report provided error."""
622
623
def get_exit_code(self) -> int:
624
"""Returns EXIT_CODE_INVALID_PROVIDED_REPORT (70)"""
625
```
626
627
This comprehensive error handling documentation provides developers with all the information needed to implement robust error handling when integrating Safety CLI into their applications and automation workflows.