Source code for hubvault.entry.base

"""
Click command utilities and exception helpers for the :mod:`hubvault.entry` package.

This module provides standardized exception classes and a command wrapper that
integrates with :mod:`click` to ensure consistent error reporting in CLI
commands. It also includes a utility to print detailed exception information
with tracebacks for unexpected errors.

The main public components are:

* :class:`ClickWarningException` - Warning-style Click exception with yellow output
* :class:`ClickErrorException` - Error-style Click exception with red output
* :class:`KeyboardInterrupted` - Specialized warning for keyboard interrupts
* :func:`print_exception` - Pretty-print exception details with traceback
* :func:`command_wrap` - Decorator to wrap Click commands with standardized handling

Example::

    >>> import click
    >>> from hubvault.entry.base import command_wrap
    >>>
    >>> @click.command()
    ... @command_wrap()
    ... def main():
    ...     raise ValueError("Boom")
    ...
    >>> # Running the command prints a formatted error and exits.

"""

import builtins
import itertools
import os
import sys
import traceback
from functools import wraps, partial
from typing import Optional, IO, Callable, TypeVar

try:
    from typing import ParamSpec
except ImportError:
    from typing_extensions import ParamSpec

import click
from click.exceptions import ClickException

from .style import echo

CONTEXT_SETTINGS = dict(
    help_option_names=['-h', '--help']
)

P = ParamSpec("P")
R = TypeVar("R")


[docs] class ClickWarningException(ClickException): """ Custom exception class for displaying warnings in yellow color. :param message: The error message. :type message: str Example:: >>> err = ClickWarningException("warn") >>> err.format_message() 'warn' """
[docs] def show(self, file: Optional[IO] = None) -> None: """ Display the warning message in yellow. :param file: File to write the output to. This parameter is ignored and output is always written to ``sys.stderr``. :type file: Optional[IO] :return: ``None``. :rtype: None Example:: >>> err = ClickWarningException("warn") >>> err.show() # doctest: +SKIP """ echo(self.format_message(), tone="warning", file=sys.stderr)
[docs] class ClickErrorException(ClickException): """ Custom exception class for displaying errors in red color. :param message: The error message. :type message: str Example:: >>> err = ClickErrorException("error") >>> err.format_message() 'error' """
[docs] def show(self, file: Optional[IO] = None) -> None: """ Display the error message in red. :param file: File to write the output to. This parameter is ignored and output is always written to ``sys.stderr``. :type file: Optional[IO] :return: ``None``. :rtype: None Example:: >>> err = ClickErrorException("error") >>> err.show() # doctest: +SKIP """ echo(self.format_message(), tone="error", file=sys.stderr)
[docs] class KeyboardInterrupted(ClickWarningException): """ Exception class for handling keyboard interruptions. This exception is raised when the wrapped Click command receives a :class:`KeyboardInterrupt`. It is a warning-level exception with a specific exit code. :param msg: Custom message to display. Defaults to ``"Interrupted."``. :type msg: Optional[str] Example:: >>> err = KeyboardInterrupted() >>> err.exit_code 7 """ exit_code = 0x7
[docs] def __init__(self, msg: Optional[str] = None) -> None: """ Initialize the exception. :param msg: Custom message to display. Defaults to ``"Interrupted."``. :type msg: Optional[str] :return: ``None``. :rtype: None Example:: >>> err = KeyboardInterrupted() >>> err.format_message() 'Interrupted.' """ ClickWarningException.__init__(self, msg or 'Interrupted.')
[docs] def command_wrap() -> Callable[[Callable[P, R]], Callable[P, R]]: """ Decorator factory for wrapping Click commands with consistent error handling. The wrapper provides the following behavior: * Re-raises :class:`click.ClickException` without modification. * Converts :class:`KeyboardInterrupt` into :class:`KeyboardInterrupted`. * For any other exception, prints a red error header, outputs a traceback using :func:`print_exception`, and exits the current Click context with exit code ``1``. :return: A decorator that wraps Click command functions. :rtype: Callable[[Callable[..., R]], Callable[..., R]] Example:: >>> import click >>> from hubvault.entry.base import command_wrap >>> >>> @click.command() ... @command_wrap() ... def main(): ... raise RuntimeError("Unexpected") ... >>> # Running the command emits a formatted error and exits with code 1. """ def _decorator(func: Callable[P, R]) -> Callable[P, R]: """ Wrap a single command callback with standardized error handling. :param func: Original command callback :type func: Callable[P, R] :return: Wrapped callback :rtype: Callable[P, R] Example:: >>> import click >>> wrapper = command_wrap() >>> wrapped = wrapper(click.Command("demo", callback=lambda: None)) >>> callable(wrapped) True """ @wraps(func) def _new_func(*args: P.args, **kwargs: P.kwargs) -> R: """ Invoke the wrapped command callback. :param args: Positional arguments forwarded to the wrapped callback :type args: P.args :param kwargs: Keyword arguments forwarded to the wrapped callback :type kwargs: P.kwargs :return: Callback return value :rtype: R :raises click.ClickException: Re-raised for expected Click errors. :raises KeyboardInterrupted: Raised when a keyboard interrupt is converted into the public CLI exception. Example:: >>> import click >>> @command_wrap() ... def callback(): ... return "ok" >>> callback() 'ok' """ try: return func(*args, **kwargs) except ClickException: raise except KeyboardInterrupt: raise KeyboardInterrupted # CLI dispatch is the outermost process boundary. Convert unexpected # runtime failures into a visible error instead of leaking a raw traceback. except Exception as err: echo("Unexpected error found when running hubvault!", tone="error", file=sys.stderr) print_exception(err, partial(echo, tone="error", file=sys.stderr)) click.get_current_context().exit(1) return _new_func return _decorator