CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cleo

Cleo allows you to create beautiful and testable command-line interfaces.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

exceptions.mddocs/

Exception Handling System

The exception handling system provides a comprehensive hierarchy of exception classes for different error conditions in CLI applications. These exceptions enable proper error handling, user feedback, and application exit codes.

Capabilities

Base Exception Class

The root exception class for all Cleo-specific errors with exit code support.

class CleoError(Exception):
    """Base exception class for all Cleo errors."""
    
    exit_code: int | None = None
    
    def __init__(self, message: str = "", exit_code: int | None = None) -> None:
        """
        Create a Cleo error.
        
        Args:
            message (str): Error message
            exit_code (int | None): Suggested exit code for the application
        """
        super().__init__(message)
        if exit_code is not None:
            self.exit_code = exit_code

Configuration and Logic Errors

Errors related to incorrect configuration or programming logic issues.

class CleoLogicError(CleoError):
    """
    Raised when there is error in command arguments
    and/or options configuration logic.
    """
    pass

Runtime Execution Errors

Errors that occur during command execution at runtime.

class CleoRuntimeError(CleoError):
    """
    Raised when command is called with invalid options or arguments.
    """
    pass

Value and Type Errors

Errors related to incorrect values or type mismatches.

class CleoValueError(CleoError):
    """
    Raised when wrong value was given to Cleo components.
    """
    pass

User Input Errors

Base class for errors caused by user input issues.

class CleoUserError(CleoError):
    """
    Base exception for user input errors.
    
    These errors are typically caused by incorrect user input
    and should result in helpful error messages.
    """
    pass

Specific User Input Error Types

Specialized exceptions for common user input problems.

class CleoNoSuchOptionError(CleoError):
    """
    Exception for undefined or invalid options.
    
    Raised when command does not have given option.
    """
    pass

class CleoMissingArgumentsError(CleoUserError):
    """
    Exception for missing required arguments.
    
    Raised when called command was not given required arguments.
    """
    pass

class CleoCommandNotFoundError(CleoUserError):
    """
    Exception for unknown commands.
    
    Raised when called command does not exist.
    """
    
    def __init__(self, name: str, commands: list[str] | None = None) -> None:
        """
        Create command not found error with optional command suggestions.
        
        Args:
            name (str): The command name that was not found
            commands (list[str] | None): Available commands for suggestions
        """

class CleoNamespaceNotFoundError(CleoUserError):
    """
    Exception for unknown command namespaces.
    
    Raised when called namespace has no commands.
    """
    
    def __init__(self, name: str, namespaces: list[str] | None = None) -> None:
        """
        Create namespace not found error with optional namespace suggestions.
        
        Args:
            name (str): The namespace name that was not found
            namespaces (list[str] | None): Available namespaces for suggestions
        """

Usage Examples

Basic Exception Handling in Commands

from cleo.commands.command import Command
from cleo.exceptions import CleoError, CleoUserError, CleoRuntimeError

class ProcessCommand(Command):
    name = "process"
    description = "Process data files"
    
    def handle(self):
        try:
            filename = self.argument("file")
            
            # Validate input
            if not filename:
                raise CleoUserError("Filename is required", exit_code=1)
            
            if not os.path.exists(filename):
                raise CleoUserError(f"File '{filename}' not found", exit_code=2)
            
            # Process file
            result = self.process_file(filename)
            
            if not result:
                raise CleoRuntimeError("Processing failed", exit_code=3)
            
            self.line(f"<info>Processed {filename} successfully</info>")
            return 0
            
        except CleoUserError as e:
            self.line(f"<error>Error: {e}</error>")
            return e.exit_code or 1
            
        except CleoRuntimeError as e:
            self.line(f"<error>Runtime error: {e}</error>")
            return e.exit_code or 1
            
        except Exception as e:
            self.line(f"<error>Unexpected error: {e}</error>")
            return 1
    
    def process_file(self, filename):
        # File processing logic
        try:
            with open(filename, 'r') as f:
                data = f.read()
            
            if len(data) == 0:
                raise CleoValueError("File is empty")
            
            # Process data...
            return True
            
        except IOError as e:
            raise CleoRuntimeError(f"Failed to read file: {e}")

Application-Level Exception Handling

from cleo.application import Application
from cleo.exceptions import CleoCommandNotFoundError, CleoNamespaceNotFoundError

class MyApplication(Application):
    def run(self, input=None, output=None, error_output=None):
        try:
            return super().run(input, output, error_output)
            
        except CleoCommandNotFoundError as e:
            self.render_error(f"Command not found: {e}", output or error_output)
            
            # Suggest similar commands
            similar = self.find_similar_commands(str(e))
            if similar:
                self.render_error(f"Did you mean: {', '.join(similar)}", output or error_output)
            
            return 1
            
        except CleoNamespaceNotFoundError as e:
            self.render_error(f"Namespace not found: {e}", output or error_output)
            return 1
            
        except KeyboardInterrupt:
            self.render_error("\nOperation cancelled by user", output or error_output)
            return 130  # Standard exit code for SIGINT
            
        except Exception as e:
            self.render_error(f"Unexpected error: {e}", output or error_output)
            return 1
    
    def render_error(self, message, output):
        if output:
            output.write_line(f"<error>{message}</error>")

Custom Exception Classes

from cleo.exceptions import CleoUserError, CleoRuntimeError

class DatabaseConnectionError(CleoRuntimeError):
    """Exception for database connection failures."""
    
    def __init__(self, host, port, message="Database connection failed"):
        super().__init__(f"{message}: {host}:{port}", exit_code=10)

class ConfigurationError(CleoUserError):
    """Exception for configuration file errors."""
    
    def __init__(self, config_file, issue):
        super().__init__(f"Configuration error in {config_file}: {issue}", exit_code=5)

class ValidationError(CleoValueError):
    """Exception for input validation failures."""
    
    def __init__(self, field, value, expected):
        super().__init__(
            f"Invalid value '{value}' for {field}. Expected: {expected}",
            exit_code=2
        )

# Usage in commands
class DatabaseCommand(Command):
    def handle(self):
        try:
            host = self.option("host")
            port = int(self.option("port"))
            
            # Validate port
            if not 1 <= port <= 65535:
                raise ValidationError("port", port, "1-65535")
            
            # Connect to database
            if not self.connect_database(host, port):
                raise DatabaseConnectionError(host, port)
            
            self.line("<info>Database connection successful</info>")
            
        except ValidationError as e:
            self.line(f"<error>{e}</error>")
            return e.exit_code
            
        except DatabaseConnectionError as e:
            self.line(f"<error>{e}</error>")
            self.line("<comment>Check your database server and network connectivity</comment>")
            return e.exit_code

Exception Handling with Recovery

class BackupCommand(Command):
    def handle(self):
        backup_locations = ['/primary/backup', '/secondary/backup', '/tertiary/backup']
        
        for i, location in enumerate(backup_locations):
            try:
                self.create_backup(location)
                self.line(f"<info>Backup created at {location}</info>")
                return 0
                
            except CleoRuntimeError as e:
                self.line(f"<comment>Backup failed at {location}: {e}</comment>")
                
                if i == len(backup_locations) - 1:
                    # Last location, no more fallbacks
                    self.line("<error>All backup locations failed</error>")
                    return e.exit_code or 1
                else:
                    # Try next location
                    self.line(f"<info>Trying next location...</info>")
                    continue
    
    def create_backup(self, location):
        if not os.path.exists(location):
            raise CleoRuntimeError(f"Backup location does not exist: {location}")
        
        if not os.access(location, os.W_OK):
            raise CleoRuntimeError(f"No write access to backup location: {location}")
        
        # Create backup...

Exception Handling in Testing

import pytest
from cleo.testers.command_tester import CommandTester
from cleo.exceptions import CleoUserError, CleoRuntimeError

def test_command_error_handling():
    command = ProcessCommand()
    tester = CommandTester(command)
    
    # Test missing argument error
    exit_code = tester.execute("")
    assert exit_code == 1
    assert "Filename is required" in tester.get_display()
    
    # Test file not found error
    exit_code = tester.execute("nonexistent.txt")
    assert exit_code == 2
    assert "File 'nonexistent.txt' not found" in tester.get_display()

def test_custom_exception_handling():
    command = DatabaseCommand()
    tester = CommandTester(command)
    
    # Test invalid port
    exit_code = tester.execute("--port 99999")
    assert exit_code == 2
    assert "Invalid value '99999' for port" in tester.get_display()

def test_exception_propagation():
    """Test that exceptions are properly caught and handled."""
    command = FailingCommand()
    tester = CommandTester(command)
    
    # Should handle exception gracefully
    exit_code = tester.execute("")
    assert exit_code != 0  # Should indicate failure
    assert "error" in tester.get_display().lower()  # Should show error message

Global Exception Handler

import sys
import logging
from cleo.exceptions import CleoError

def setup_global_exception_handler():
    """Set up global exception handling for the application."""
    
    def handle_exception(exc_type, exc_value, exc_traceback):
        if isinstance(exc_value, CleoError):
            # Cleo exceptions are handled by the application
            return
        
        # Log unexpected exceptions
        logging.error(
            "Uncaught exception",
            exc_info=(exc_type, exc_value, exc_traceback)
        )
        
        # Show user-friendly message
        print("An unexpected error occurred. Please check the logs for details.")
        sys.exit(1)
    
    sys.excepthook = handle_exception

# Use in main application
if __name__ == "__main__":
    setup_global_exception_handler()
    
    app = MyApplication()
    try:
        exit_code = app.run()
        sys.exit(exit_code)
    except KeyboardInterrupt:
        print("\nOperation cancelled by user")
        sys.exit(130)

Error Reporting and Logging

import logging
from cleo.exceptions import CleoError

class ReportingCommand(Command):
    def __init__(self):
        super().__init__()
        self.logger = logging.getLogger(__name__)
    
    def handle(self):
        try:
            # Command logic here
            self.process_data()
            
        except CleoUserError as e:
            # User errors don't need full logging
            self.line(f"<error>{e}</error>")
            return e.exit_code or 1
            
        except CleoRuntimeError as e:
            # Runtime errors should be logged for debugging
            self.logger.error(f"Runtime error in {self.name}: {e}")
            self.line(f"<error>Operation failed: {e}</error>")
            return e.exit_code or 1
            
        except Exception as e:
            # Unexpected errors need full logging
            self.logger.exception(f"Unexpected error in {self.name}")
            self.line("<error>An unexpected error occurred. Check logs for details.</error>")
            return 1
    
    def process_data(self):
        # Processing logic that might raise exceptions
        pass

Install with Tessl CLI

npx tessl i tessl/pypi-cleo

docs

application.md

commands.md

exceptions.md

index.md

io.md

styling.md

testing.md

ui.md

tile.json