Skip to content

Shutdown

provide.foundation.cli.shutdown

TODO: Add module docstring.

Functions

register_cleanup_handlers

register_cleanup_handlers(
    *, manage_signals: bool = True
) -> None

Register signal handlers and atexit cleanup.

This should be called once at CLI startup to ensure cleanup happens on all exit paths.

Parameters:

Name Type Description Default
manage_signals bool

If True, register signal handlers. If False, only register atexit cleanup. Set to False if an application built with this framework is being used as a library by another process that manages its own signals.

True
Note

Signal handlers are automatically restored during cleanup.

Source code in provide/foundation/cli/shutdown.py
def register_cleanup_handlers(*, manage_signals: bool = True) -> None:
    """Register signal handlers and atexit cleanup.

    This should be called once at CLI startup to ensure cleanup
    happens on all exit paths.

    Args:
        manage_signals: If True, register signal handlers. If False, only
            register atexit cleanup. Set to False if an application built
            with this framework is being used as a library by another process
            that manages its own signals.

    Note:
        Signal handlers are automatically restored during cleanup.

    """
    global _original_sigint_handler, _original_sigterm_handler, _handlers_registered

    # Register atexit cleanup
    atexit.register(_cleanup_foundation_resources)

    # Register signal handlers if requested
    if manage_signals:
        # Save original handlers
        _original_sigint_handler = signal.getsignal(signal.SIGINT)

        # SIGTERM only exists on Unix-like systems
        if sys.platform != "win32":
            _original_sigterm_handler = signal.getsignal(signal.SIGTERM)

        # Register our handlers
        signal.signal(signal.SIGINT, _signal_handler)

        # Register SIGTERM handler on Unix only
        if sys.platform != "win32":
            signal.signal(signal.SIGTERM, _signal_handler)

        _handlers_registered = True
        log.trace("Registered cleanup handlers with signal management")
    else:
        log.trace("Registered cleanup handlers (signal management disabled)")

unregister_cleanup_handlers

unregister_cleanup_handlers() -> None

Unregister cleanup handlers (mainly for testing).

Restores original signal handlers and removes atexit hook.

Source code in provide/foundation/cli/shutdown.py
def unregister_cleanup_handlers() -> None:
    """Unregister cleanup handlers (mainly for testing).

    Restores original signal handlers and removes atexit hook.
    """
    global _cleanup_executed

    # Restore original signal handlers
    _restore_signal_handlers()

    # Remove atexit handler
    with contextlib.suppress(ValueError):
        atexit.unregister(_cleanup_foundation_resources)

    # Reset cleanup flag
    _cleanup_executed = False

    log.trace("Unregister cleanup handlers")

with_cleanup

with_cleanup(func: Callable[P, R]) -> Callable[P, R]

Decorator to ensure cleanup on CLI command exit.

Wraps a CLI command function to: 1. Handle KeyboardInterrupt gracefully 2. Ensure cleanup on all exit paths 3. Provide consistent error handling

Example

@click.command() @with_cleanup def my_command(ctx: click.Context) -> None: # Command implementation pass

Parameters:

Name Type Description Default
func Callable[P, R]

CLI command function to wrap

required

Returns:

Type Description
Callable[P, R]

Wrapped function with cleanup

Source code in provide/foundation/cli/shutdown.py
def with_cleanup(func: Callable[P, R]) -> Callable[P, R]:
    """Decorator to ensure cleanup on CLI command exit.

    Wraps a CLI command function to:
    1. Handle KeyboardInterrupt gracefully
    2. Ensure cleanup on all exit paths
    3. Provide consistent error handling

    Example:
        @click.command()
        @with_cleanup
        def my_command(ctx: click.Context) -> None:
            # Command implementation
            pass

    Args:
        func: CLI command function to wrap

    Returns:
        Wrapped function with cleanup

    """

    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        try:
            # Execute the command
            return func(*args, **kwargs)

        except KeyboardInterrupt:
            # Handle Ctrl+C gracefully
            try:
                # Try to use click for pretty output if available
                from provide.foundation.cli.deps import click

                click.echo("\n⛔ Command interrupted by user")
            except ImportError:
                # Use Foundation's console output
                perr("\n⛔ Command interrupted by user")

            # Cleanup will be handled by atexit/signal handlers
            sys.exit(EXIT_SIGINT)

        except Exception as e:
            # Log unexpected errors
            log.error("Command failed with unexpected error", error=str(e), exc_info=True)

            # Cleanup will be handled by atexit
            raise

    return wrapper