Source code for pyfcstm.entry.base

"""
Click command utilities and exception helpers for the :mod:`pyfcstm.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 pyfcstm.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

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 """
[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] """ click.secho(self.format_message(), fg='yellow', file=sys.stderr)
[docs] class ClickErrorException(ClickException): """ Custom exception class for displaying errors in red color. :param message: The error message. :type message: str """
[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] """ click.secho(self.format_message(), fg='red', 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] """ 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] """ 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 pyfcstm.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]: @wraps(func) def _new_func(*args: P.args, **kwargs: P.kwargs) -> R: try: return func(*args, **kwargs) except ClickException: # Click's own user-facing errors already format themselves. raise except KeyboardInterrupt: # Translate Ctrl-C into our own typed sentinel so caller code # can pattern-match on it. raise KeyboardInterrupted except BaseException as err: # Top-of-stack CLI barrier: format *any* remaining failure # (``Exception`` subclasses AND ``SystemExit``-style escapes # that aren't ``KeyboardInterrupt``) into a friendly red # message and exit non-zero. This is the only place in the # codebase where ``BaseException`` is acceptable — it is the # last-resort process boundary for the ``pyfcstm`` console # script. The full traceback is still printed via # ``print_exception`` so the underlying bug stays diagnosable. click.secho('Unexpected error found when running pyfcstm!', fg='red', file=sys.stderr) print_exception(err, partial(click.secho, fg='red', file=sys.stderr)) click.get_current_context().exit(1) return _new_func return _decorator