Exception Classes

Overview

The keychain library provides a comprehensive exception hierarchy for error handling. All keychain-specific exceptions inherit from the base KeychainError class, which provides error code support and standardized error messaging.

Package: keychain.exceptions

from keychain.exceptions import (
    KeychainError,
    KeychainInitializationError,
    KeychainValidationError,
    KeychainMemoryError,
    KeychainSecurityError,
    KeychainNotFoundError,
    KeychainPermissionError
)

Exception Hierarchy

The keychain exception hierarchy is structured as follows:

Exception
└── KeychainError (base exception)
    ├── KeychainInitializationError
    ├── KeychainValidationError
    ├── KeychainMemoryError
    ├── KeychainSecurityError
    ├── KeychainNotFoundError
    └── KeychainPermissionError

Base Exception Class

KeychainError

class KeychainError(Exception):
    """Base exception for all keychain library errors."""

The base exception class for all keychain-specific errors. Provides error code support and standardized error messaging.

Constructor

def __init__(self, message: str, error_code: Optional[int] = None) -> None

Initialize a KeychainError.

Parameters:

  • message (str) - The error message

  • error_code (int, optional) - Optional error code from the C library

Example:

# Create error with message only
error = KeychainError("Operation failed")

# Create error with message and error code
error = KeychainError("Validation failed", error_code=1)

Properties

message
@property
def message(self) -> str

Get the error message.

Returns: The error message string

error_code
@property
def error_code(self) -> Optional[int]

Get the error code.

Returns: The error code if available, None otherwise

String Representation

def __str__(self) -> str

String representation of the error.

Returns: Error message with error code if available (format: "[code] message")

Example:

error = KeychainError("Operation failed", error_code=1)
print(str(error))  # Output: [1] Operation failed

error_no_code = KeychainError("Operation failed")
print(str(error_no_code))  # Output: Operation failed

Specific Exception Classes

KeychainInitializationError

class KeychainInitializationError(KeychainError):
    """Raised when the keychain library fails to initialize."""

Raised when the keychain library fails to initialize properly. This typically occurs during library loading or when setting up the C library bindings.

Example:

try:
    # Library initialization code
    pass
except KeychainInitializationError as e:
    print(f"Failed to initialize keychain library: {e}")

KeychainValidationError

class KeychainValidationError(KeychainError):
    """Raised when input validation fails."""

Raised when input validation fails. This includes invalid parameters, malformed data, or data that doesn’t meet the required constraints.

Example:

try:
    # Validation of user input
    did = DID("invalid:did:format")
except KeychainValidationError as e:
    print(f"Validation error: {e}")

KeychainMemoryError

class KeychainMemoryError(KeychainError):
    """Raised when memory allocation or management fails."""

Raised when memory allocation or management operations fail. This typically indicates insufficient memory or memory corruption issues.

Example:

try:
    # Memory-intensive operation
    large_credential = Credential(very_large_data)
except KeychainMemoryError as e:
    print(f"Memory error: {e}")

KeychainSecurityError

class KeychainSecurityError(KeychainError):
    """Raised when cryptographic operations fail."""

Raised when cryptographic operations fail. This includes signature verification failures, encryption/decryption errors, and other security-related issues.

Example:

try:
    # Cryptographic operation
    verified = credential.verify_signature()
except KeychainSecurityError as e:
    print(f"Security error: {e}")

KeychainNotFoundError

class KeychainNotFoundError(KeychainError):
    """Raised when a requested resource is not found."""

Raised when a requested resource is not found. This includes missing entities, non-existent identifiers, or unavailable resources.

Example:

try:
    # Resource lookup
    contact = gateway.get_contact("nonexistent_id")
except KeychainNotFoundError as e:
    print(f"Resource not found: {e}")

KeychainPermissionError

class KeychainPermissionError(KeychainError):
    """Raised when access is denied to a resource."""

Raised when access is denied to a resource. This includes insufficient permissions, authorization failures, and access control violations.

Example:

try:
    # Protected operation
    result = gateway.perform_admin_operation()
except KeychainPermissionError as e:
    print(f"Permission denied: {e}")

Error Code Translation

translate_error_code()

def translate_error_code(error_code: int, message: str = "") -> KeychainError

Translate C library error codes to appropriate Python exceptions.

Parameters:

  • error_code (int) - The error code from the C library

  • message (str, optional) - Optional error message

Returns: Appropriate KeychainError subclass instance

Error Code Mappings:

  • 1KeychainValidationError

  • 2KeychainNotFoundError

  • 3KeychainPermissionError

  • 4KeychainMemoryError

  • 5KeychainSecurityError

  • 99KeychainInitializationError

  • Other codes → KeychainError

Example:

from keychain.exceptions import translate_error_code

# Translate error code from C library
error = translate_error_code(1, "Invalid input provided")
# Returns: KeychainValidationError("[1] Invalid input provided")

# Translate without custom message
error = translate_error_code(4)
# Returns: KeychainMemoryError("[4] Operation failed with error code 4")

Example: Exception Handling

from keychain.exceptions import (
    KeychainError,
    KeychainValidationError,
    KeychainSecurityError,
    KeychainNotFoundError
)
from keychain.core.gateway import Gateway
from keychain.identity.did import DID

def safe_keychain_operation():
    """Demonstrate comprehensive exception handling."""

    try:
        # Initialize gateway
        gateway = Gateway()

        # Validate and create DID
        did_string = "did:example:123456"
        did = DID(did_string)

        # Perform operations that might fail
        contact = gateway.get_contact(did)

        # Security-sensitive operation
        result = contact.verify_credentials()

        print("Operation completed successfully")
        return result

    except KeychainValidationError as e:
        print(f"Validation error: {e}")
        if e.error_code:
            print(f"Error code: {e.error_code}")
        return None

    except KeychainSecurityError as e:
        print(f"Security error: {e}")
        # Log security issues for audit
        print("Security operation failed - check credentials")
        return None

    except KeychainNotFoundError as e:
        print(f"Resource not found: {e}")
        # Handle missing resources gracefully
        return None

    except KeychainError as e:
        print(f"General keychain error: {e}")
        # Handle any other keychain-specific errors
        return None

    except Exception as e:
        print(f"Unexpected error: {e}")
        # Handle non-keychain errors
        return None

# Usage
result = safe_keychain_operation()
if result:
    print("Operation succeeded")
else:
    print("Operation failed")

Example: Custom Error Handling

from keychain.exceptions import KeychainError, translate_error_code
from keychain.core.gateway import Gateway

class KeychainOperationManager:
    """Manager with comprehensive error handling."""

    def __init__(self):
        self.gateway = None
        self.error_log = []

    def initialize(self) -> bool:
        """Initialize with error handling."""
        try:
            self.gateway = Gateway()
            return True
        except KeychainError as e:
            self._log_error("Initialization failed", e)
            return False

    def _log_error(self, operation: str, error: KeychainError) -> None:
        """Log error with details."""
        error_entry = {
            "operation": operation,
            "message": error.message,
            "error_code": error.error_code,
            "exception_type": type(error).__name__
        }
        self.error_log.append(error_entry)

        print(f"Error in {operation}: {error}")

    def handle_c_library_error(self, error_code: int, operation: str) -> None:
        """Handle error codes from C library."""
        try:
            # Translate C error code to Python exception
            exception = translate_error_code(error_code, f"C library error in {operation}")
            raise exception
        except KeychainError as e:
            self._log_error(operation, e)

    def safe_operation(self, operation_func, *args, **kwargs):
        """Execute operation with comprehensive error handling."""
        if not self.gateway:
            print("Gateway not initialized")
            return None

        try:
            return operation_func(*args, **kwargs)
        except KeychainValidationError as e:
            self._log_error("Validation", e)
        except KeychainSecurityError as e:
            self._log_error("Security", e)
        except KeychainNotFoundError as e:
            self._log_error("Resource lookup", e)
        except KeychainError as e:
            self._log_error("General keychain operation", e)
        except Exception as e:
            # Convert unexpected errors to KeychainError
            keychain_error = KeychainError(f"Unexpected error: {str(e)}")
            self._log_error("Unexpected error", keychain_error)

        return None

    def get_error_summary(self) -> dict:
        """Get summary of all errors encountered."""
        error_counts = {}
        for error in self.error_log:
            exc_type = error["exception_type"]
            error_counts[exc_type] = error_counts.get(exc_type, 0) + 1

        return {
            "total_errors": len(self.error_log),
            "error_types": error_counts,
            "recent_errors": self.error_log[-5:] if self.error_log else []
        }

# Usage example
def error_handling_example():
    """Demonstrate error handling manager."""

    manager = KeychainOperationManager()

    if not manager.initialize():
        print("Failed to initialize")
        return

    # Example operations with error handling
    result1 = manager.safe_operation(lambda: DID("invalid:format"))
    result2 = manager.safe_operation(lambda: manager.gateway.get_contact("nonexistent"))

    # Handle C library error
    manager.handle_c_library_error(1, "validate_input")
    manager.handle_c_library_error(5, "cryptographic_operation")

    # Get error summary
    summary = manager.get_error_summary()
    print(f"Error summary: {summary}")

if __name__ == "__main__":
    error_handling_example()

Example: Exception Context Management

from keychain.exceptions import KeychainError
from keychain.core.gateway import Gateway
from contextlib import contextmanager
from typing import Generator, Optional

@contextmanager
def keychain_context() -> Generator[Optional[Gateway], None, None]:
    """Context manager for keychain operations with automatic cleanup."""

    gateway = None
    try:
        # Initialize gateway
        gateway = Gateway()
        yield gateway

    except KeychainError as e:
        print(f"Keychain error in context: {e}")
        yield None

    except Exception as e:
        print(f"Unexpected error in context: {e}")
        yield None

    finally:
        # Cleanup if needed
        if gateway:
            try:
                # Perform cleanup operations
                pass
            except KeychainError as e:
                print(f"Cleanup error: {e}")

def context_usage_example():
    """Demonstrate context manager usage."""

    with keychain_context() as gateway:
        if gateway is None:
            print("Failed to initialize gateway")
            return

        try:
            # Perform operations within context
            persona = gateway.get_default_persona()
            if persona:
                print(f"Default persona: {persona}")

        except KeychainError as e:
            print(f"Operation failed: {e}")

if __name__ == "__main__":
    context_usage_example()

Error Handling Best Practices

  1. Catch Specific Exceptions: Always catch the most specific exception type first, then broader ones.

  2. Use Error Codes: When available, use error codes for programmatic error handling.

  3. Log Security Errors: Always log security-related errors for audit purposes.

  4. Graceful Degradation: Handle errors gracefully and provide fallback behavior when possible.

  5. Context Information: Include operation context in error messages for better debugging.

Example:

try:
    # Risky operation
    result = perform_operation()
except KeychainSecurityError as e:
    # Log security error
    security_logger.error(f"Security violation: {e}")
    # Notify security team
    notify_security_team(e)
    # Graceful fallback
    result = get_cached_result()
except KeychainValidationError as e:
    # Handle validation errors
    user_logger.info(f"User input error: {e}")
    # Prompt for correct input
    result = prompt_user_correction()
except KeychainError as e:
    # Handle general keychain errors
    system_logger.error(f"Keychain operation failed: {e}")
    result = None

See Also