Error Handling

Error handler functions can be registered to automatically handle exceptions that were not caught inside Commands. The error handler that should be used for a given Command can be specified through CommandConfig or, more easily, through the error_handler class keyword argument when defining a Command.

All ErrorHandlers will catch CommandParserException exceptions and handle them by calling the exception’s exit() method.

There are three ErrorHandler instances included by default:

  1. error_handler:
  1. extended_error_handler:
    • The default error handler

    • Handles everything that error_handler does

    • The only difference between this handler and error_handler is that an additional handler is registered only when running on Windows to catch OSError and work around a bug related to the broken pipe error number: handle_win_os_pipe_error()

  1. no_exit_handler:

Configuration

Example of how to use error_handler instead of extended_error_handler:

from cli_command_parser import Command, Option, error_handler

class MyCommand(Command, error_handler=error_handler):
    foo = Option()

Example of how to disable all error handling, including the handler for CommandParserException:

from cli_command_parser import Command, Option

class MyCommand(Command, error_handler=None):
    foo = Option()

Defining Error Handlers

To define an error handler function for an additional exception class, the ErrorHandler object can be used as a decorator on the function that will handle the exception.

Similar to object.__exit__(), if the error handler function returns a truthy value, then the exception will be considered handled. Behavior differs in the case of 0, which will be treated as a value with which sys.exit() should be called. In fact, sys.exit() will be called with any truthy value other than True that is returned by the error handler function.

To indicate that the error was not handled, and that any other (less specific) error handlers that may match the exception should be tried, the handler function should return False or any other falsey value (other than 0).

Alternatively, the handler function may call sys.exit() directly, if desired. Similar to __exit__, the exception that was passed to the handler function should NOT be re-raised by the handler function.

If no handler function can be found for a given exception, or if none of them indicate that the exception was handled or should result in a call to sys.exit(), then the exception will be propagated as usual (which should result in the traceback being printed by the interpreter).

Example of adding a handler for a custom MyException exception to the default extended_error_handler:

import sys
from cli_command_parser import extended_error_handler

@extended_error_handler(MyException)
def handle_my_exception(exc: MyException):
    print(f'Unable to proceed due to {exc}', file=sys.stderr)
    return 1

Advanced

For repos that contain many separate entry points defined in a cli package or similar, a common error handler function can be defined / registered in the package’s __init__.py. This will result in that handler function being used automatically for all of the modules within that package (and its sub-packages) without needing to explicitly import it in any of them.

One example use case for that approach would be for user-facing scripts to have a catch-all handler registered for Exception, where the handler logs just the error message by default, and only logs the full traceback when the user specified --verbose output or similar.