Scan dependencies for known vulnerabilities and licenses.
Overall
score
61%
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.
Import Statements:
from safety.errors import (
# Base exception classes
SafetyError, SafetyException,
# Database and connectivity errors
DatabaseFetchError, DatabaseFileNotFoundError, MalformedDatabase,
NetworkConnectionError, RequestTimeoutError, ServerError, TooManyRequestsError,
# Authentication and validation errors
InvalidCredentialError, NotVerifiedEmailError, InvalidRequirementError,
InvalidProvidedReportError
)
from safety.constants import (
EXIT_CODE_FAILURE, EXIT_CODE_VULNERABILITIES_FOUND,
EXIT_CODE_INVALID_AUTH_CREDENTIAL, EXIT_CODE_INVALID_REQUIREMENT,
EXIT_CODE_MALFORMED_DB, EXIT_CODE_TOO_MANY_REQUESTS,
EXIT_CODE_UNABLE_TO_FETCH_VULNERABILITY_DB,
EXIT_CODE_UNABLE_TO_LOAD_LOCAL_VULNERABILITY_DB,
EXIT_CODE_EMAIL_NOT_VERIFIED, EXIT_CODE_INVALID_PROVIDED_REPORT
)Description: Base exception class for all Safety CLI errors with formatted messaging.
class SafetyException(Exception):
"""Base exception for Safety CLI errors with template messaging."""
def __init__(self,
message: str = "Unhandled exception happened: {info}",
info: str = "") -> None:
"""
Initialize Safety exception with formatted message.
Args:
message (str): Error message template with {info} placeholder
info (str): Additional information for message template
"""
def get_exit_code(self) -> int:
"""
Get the exit code associated with this exception.
Returns:
int: Exit code (default: EXIT_CODE_FAILURE = 1)
"""Example Usage:
from safety.errors import SafetyException
# Basic exception with default message
raise SafetyException(info="Database connection failed")
# Custom message template
raise SafetyException(
message="Scan failed for project {info}",
info="my-project-v1.0"
)
# Handle exception and get exit code
try:
# Safety operation
pass
except SafetyException as e:
print(f"Error: {e.message}")
exit(e.get_exit_code())Description: Generic Safety CLI error with optional error codes.
class SafetyError(Exception):
"""Generic Safety CLI error with error code support."""
def __init__(self,
message: str = "Unhandled Safety generic error",
error_code: Optional[int] = None) -> None:
"""
Initialize Safety error.
Args:
message (str): Error description
error_code (Optional[int]): Numeric error code for categorization
"""
message: str # Error message
error_code: Optional[int] # Optional error codeExample Usage:
from safety.errors import SafetyError
# Generic error
raise SafetyError("Failed to process vulnerability data")
# Error with code
raise SafetyError(
message="API rate limit exceeded",
error_code=429
)
# Handle with error code checking
try:
# Safety operation
pass
except SafetyError as e:
if e.error_code == 429:
print("Rate limited - retrying in 60 seconds")
else:
print(f"Safety error: {e.message}")Description: Raised when authentication credentials are invalid or expired.
class InvalidCredentialError(SafetyError):
"""Authentication credential validation error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_INVALID_AUTH_CREDENTIAL (2)"""Common Scenarios:
Example Usage:
from safety.errors import InvalidCredentialError
from safety.auth.utils import SafetyAuthSession
try:
session = SafetyAuthSession()
session.api_key = "invalid-key"
response = session.get("/user/profile")
except InvalidCredentialError as e:
print("Authentication failed - please run 'safety auth login'")
exit(2)Description: Network connectivity and communication errors.
class NetworkConnectionError(SafetyError):
"""Network connectivity error."""
# Common causes:
# - No internet connection
# - Proxy configuration issues
# - DNS resolution failures
# - Firewall blocking connectionsDescription: Request timeout errors for API calls and downloads.
class RequestTimeoutError(SafetyError):
"""Request timeout error."""
# Triggered when:
# - API requests exceed timeout limit
# - Database downloads are slow
# - Network latency is highDescription: Server-side errors from Safety platform APIs.
class ServerError(SafetyError):
"""Server-side error from Safety platform."""
# Indicates:
# - Safety platform maintenance
# - Internal server errors (5xx)
# - Service unavailabilityDescription: API rate limiting errors.
class TooManyRequestsError(SafetyError):
"""API rate limiting error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_TOO_MANY_REQUESTS (3)"""Example Usage:
from safety.errors import (
NetworkConnectionError, RequestTimeoutError,
ServerError, TooManyRequestsError
)
import time
def retry_with_backoff(operation, max_retries=3):
"""Retry operation with exponential backoff for network errors."""
for attempt in range(max_retries):
try:
return operation()
except TooManyRequestsError:
if attempt < max_retries - 1:
wait_time = 2 ** attempt * 60 # 60s, 120s, 240s
print(f"Rate limited - waiting {wait_time}s before retry")
time.sleep(wait_time)
else:
raise
except (NetworkConnectionError, RequestTimeoutError) as e:
if attempt < max_retries - 1:
print(f"Network error - retrying in 10s: {e}")
time.sleep(10)
else:
raise
except ServerError:
print("Safety platform unavailable - please try again later")
raiseDescription: Requirement parsing and validation errors.
class InvalidRequirementError(SafetyError):
"""Requirement parsing error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_INVALID_REQUIREMENT (4)"""Common Causes:
Example Usage:
from safety.errors import InvalidRequirementError
from safety.models import SafetyRequirement
def parse_requirement_safely(req_string: str) -> Optional[SafetyRequirement]:
"""Parse requirement with error handling."""
try:
return SafetyRequirement(req_string)
except InvalidRequirementError as e:
print(f"Invalid requirement '{req_string}': {e.message}")
return None
# Usage
requirements = [
"requests>=2.20.0",
"django>=3.0,<4.0",
"invalid-requirement-syntax!!!" # This will fail
]
valid_requirements = []
for req_str in requirements:
req = parse_requirement_safely(req_str)
if req:
valid_requirements.append(req)Safety CLI uses specific exit codes to indicate different error conditions:
from safety.constants import (
EXIT_CODE_OK, # 0 - Success
EXIT_CODE_FAILURE, # 1 - General failure
EXIT_CODE_INVALID_AUTH_CREDENTIAL, # 2 - Authentication error
EXIT_CODE_TOO_MANY_REQUESTS, # 3 - Rate limiting
EXIT_CODE_INVALID_REQUIREMENT, # 4 - Requirement parsing error
EXIT_CODE_MALFORMED_DB, # 5 - Database corruption
EXIT_CODE_UNABLE_TO_FETCH_VULNERABILITY_DB, # 6 - DB download failure
EXIT_CODE_UNABLE_TO_LOAD_LOCAL_VULNERABILITY_DB, # 7 - Local DB error
EXIT_CODE_EMAIL_NOT_VERIFIED, # 8 - Email verification required
EXIT_CODE_INVALID_PROVIDED_REPORT, # 9 - Invalid report format
EXIT_CODE_VULNERABILITIES_FOUND # 64 - Vulnerabilities detected
)
# Exit code mapping
EXIT_CODE_DESCRIPTIONS = {
0: "Success - no issues found",
1: "General failure or error",
2: "Invalid authentication credentials",
3: "Too many requests - rate limited",
4: "Invalid requirement specification",
5: "Malformed vulnerability database",
6: "Unable to fetch vulnerability database",
7: "Unable to load local vulnerability database",
8: "Email verification required",
9: "Invalid report format provided",
64: "Vulnerabilities found in dependencies"
}# Exception inheritance hierarchy
Exception
├── SafetyException (base with exit codes)
│ └── [Various specific exceptions inherit exit code behavior]
└── SafetyError (generic with error codes)
├── InvalidCredentialError
├── NetworkConnectionError
├── RequestTimeoutError
├── ServerError
├── TooManyRequestsError
└── InvalidRequirementErrorfrom safety.errors import *
import logging
def handle_safety_operation():
"""Example of comprehensive error handling for Safety operations."""
logger = logging.getLogger(__name__)
try:
# Perform Safety operation
result = safety_scan_operation()
return result
except InvalidCredentialError as e:
logger.error(f"Authentication failed: {e.message}")
print("Please run 'safety auth login' to authenticate")
return None
except TooManyRequestsError as e:
logger.warning(f"Rate limited: {e.message}")
print("API rate limit exceeded - please try again later")
return None
except NetworkConnectionError as e:
logger.error(f"Network error: {e.message}")
print("Unable to connect to Safety platform - check network connection")
return None
except InvalidRequirementError as e:
logger.error(f"Requirement parsing failed: {e.message}")
print("Invalid requirement format in dependency files")
return None
except ServerError as e:
logger.error(f"Platform error: {e.message}")
print("Safety platform is temporarily unavailable")
return None
except SafetyException as e:
logger.error(f"Safety exception: {e.message}")
print(f"Safety CLI error: {e.message}")
exit(e.get_exit_code())
except SafetyError as e:
logger.error(f"Safety error: {e.message}")
if e.error_code:
print(f"Error {e.error_code}: {e.message}")
else:
print(f"Error: {e.message}")
return None
except Exception as e:
logger.exception("Unexpected error in Safety operation")
print(f"Unexpected error: {e}")
exit(EXIT_CODE_FAILURE)from contextlib import contextmanager
from safety.errors import SafetyError
@contextmanager
def safety_operation_context(operation_name: str):
"""Context manager for Safety operations with error handling."""
try:
print(f"Starting {operation_name}...")
yield
print(f"✅ {operation_name} completed successfully")
except SafetyError as e:
print(f"❌ {operation_name} failed: {e.message}")
if hasattr(e, 'get_exit_code'):
exit(e.get_exit_code())
else:
exit(EXIT_CODE_FAILURE)
except Exception as e:
print(f"❌ {operation_name} failed unexpectedly: {e}")
exit(EXIT_CODE_FAILURE)
# Usage
with safety_operation_context("vulnerability scan"):
scan_results = perform_vulnerability_scan()
with safety_operation_context("license check"):
license_results = perform_license_check()import logging
from safety.errors import SafetyError, SafetyException
# Configure logging for error tracking
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('safety.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger('safety.errors')
def log_and_handle_error(e: Exception, context: str = ""):
"""Centralized error logging and handling."""
if isinstance(e, SafetyException):
logger.error(f"Safety exception in {context}: {e.message}")
return e.get_exit_code()
elif isinstance(e, SafetyError):
logger.error(f"Safety error in {context}: {e.message} (code: {e.error_code})")
return EXIT_CODE_FAILURE
else:
logger.exception(f"Unexpected error in {context}")
return EXIT_CODE_FAILURE#!/bin/bash
# CI/CD script with Safety error handling
set -e # Exit on any error
safety scan
SAFETY_EXIT_CODE=$?
case $SAFETY_EXIT_CODE in
0)
echo "✅ No security issues found"
;;
64)
echo "🚨 Vulnerabilities found - failing build"
exit 1
;;
2)
echo "❌ Authentication failed - check SAFETY_API_KEY"
exit 1
;;
3)
echo "⏳ Rate limited - retrying after delay"
sleep 60
safety scan
;;
*)
echo "❌ Safety scan failed with exit code $SAFETY_EXIT_CODE"
exit 1
;;
esac# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Safety
run: pip install safety
- name: Run Security Scan
env:
SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}
run: |
set -e
safety scan --output json --save-as json:security-report.json || EXIT_CODE=$?
case ${EXIT_CODE:-0} in
0)
echo "✅ No security issues found"
;;
64)
echo "🚨 Security vulnerabilities detected"
echo "::error::Security vulnerabilities found - see security-report.json"
exit 1
;;
2)
echo "::error::Authentication failed - check SAFETY_API_KEY secret"
exit 1
;;
*)
echo "::error::Safety scan failed with exit code ${EXIT_CODE:-0}"
exit 1
;;
esac
- name: Upload Security Report
if: always()
uses: actions/upload-artifact@v3
with:
name: security-report
path: security-report.jsonDescription: Vulnerability database corruption or format errors.
class MalformedDatabase(SafetyError):
"""Malformed vulnerability database error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_MALFORMED_DB (69)"""Description: Local vulnerability database file missing or inaccessible.
class DatabaseFileNotFoundError(DatabaseFetchError):
"""Local vulnerability database file not found."""Description: Account email verification required for API access.
class NotVerifiedEmailError(SafetyError):
"""Email verification required error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_EMAIL_NOT_VERIFIED (72)"""Description: Invalid report format or content provided to Safety.
class InvalidProvidedReportError(SafetyError):
"""Invalid report provided error."""
def get_exit_code(self) -> int:
"""Returns EXIT_CODE_INVALID_PROVIDED_REPORT (70)"""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.
Install with Tessl CLI
npx tessl i tessl/pypi-safetydocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10