CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-safety

Scan dependencies for known vulnerabilities and licenses.

Overall
score

61%

Overview
Eval results
Files

errors.mddocs/

Error Handling and Exceptions

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.

Core Exception Classes { .api }

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
)

SafetyException { .api }

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())

SafetyError { .api }

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 code

Example 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}")

Specific Exception Types

Authentication Errors { .api }

InvalidCredentialError { .api }

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:

  • Invalid API key
  • Expired authentication token
  • Insufficient permissions for organization
  • Email not verified

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)

Network and Connection Errors { .api }

NetworkConnectionError { .api }

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 connections

RequestTimeoutError { .api }

Description: 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 high

ServerError { .api }

Description: 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 unavailability

TooManyRequestsError { .api }

Description: 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")
            raise

Data and Parsing Errors { .api }

InvalidRequirementError { .api }

Description: 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:

  • Malformed requirement specifications
  • Invalid version constraints
  • Unsupported requirement syntax
  • Missing package names

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)

Exit Codes { .api }

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"
}

Error Handling Patterns

Exception Hierarchy { .api }

# 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
    └── InvalidRequirementError

Comprehensive Error Handling { .api }

from 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)

Error Context Management { .api }

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()

Logging Integration { .api }

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

CI/CD Error Handling

Exit Code Handling in CI/CD { .api }

#!/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 Actions Error Handling { .api }

# .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.json

Additional Error Classes { .api }

MalformedDatabase { .api }

Description: 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)"""

DatabaseFileNotFoundError { .api }

Description: Local vulnerability database file missing or inaccessible.

class DatabaseFileNotFoundError(DatabaseFetchError):
    """Local vulnerability database file not found."""

NotVerifiedEmailError { .api }

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)"""

InvalidProvidedReportError { .api }

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-safety

docs

authentication.md

cli-commands.md

configuration.md

errors.md

formatters.md

index.md

models.md

programmatic.md

scanning.md

tile.json