Skip to content

Mixins

Feature mixins used internally by pyclif's group and context classes. Exposed publicly for advanced subclassing.

GlobalOptionsMixin

Propagates options marked is_global=True from a parent group to all child commands and subgroups at invocation time.

pyclif.GlobalOptionsMixin

Mixin that propagates global options to subcommands.

Source code in src/pyclif/core/mixins/cli.py
class GlobalOptionsMixin:
    """Mixin that propagates global options to subcommands."""

    def _propagate_global_options(
        self, cmd: click_extra.Command, global_options: list[click_extra.Parameter]
    ) -> None:
        """Recursively propagate global options to a command and its subcommands.

        Args:
            cmd: The command to receive the options.
            global_options: The global options to inject.
        """
        if hasattr(cmd, "params"):
            existing_param_names = {param.name for param in cmd.params}
            for opt in global_options:
                if opt.name not in existing_param_names:
                    cmd.params.append(opt)

        # If the command is a group, recursively apply to its currently registered subcommands
        if hasattr(cmd, "commands"):
            for subcommand in cmd.commands.values():
                self._propagate_global_options(subcommand, global_options)

    # noinspection PyUnresolvedReferences
    def add_command(self, cmd: click_extra.Command, name: str | None = None, **kwargs: Any) -> None:
        """Register a subcommand and inject global options.

        Args:
            cmd: The command to add.
            name: The name to register the command with.
            **kwargs: Additional arguments passed to the parent method.
        """
        # 1. Find global options attached to this group
        global_options = [
            param for param in getattr(self, "params", []) if getattr(param, "is_global", False)
        ]

        # 2. Inject them recursively into the subcommand and all its descendants
        if global_options:
            self._propagate_global_options(cmd, global_options)

        super().add_command(cmd, name, **kwargs)  # type: ignore

add_command(cmd, name=None, **kwargs)

Register a subcommand and inject global options.

Parameters:

Name Type Description Default
cmd Command

The command to add.

required
name str | None

The name to register the command with.

None
**kwargs Any

Additional arguments passed to the parent method.

{}
Source code in src/pyclif/core/mixins/cli.py
def add_command(self, cmd: click_extra.Command, name: str | None = None, **kwargs: Any) -> None:
    """Register a subcommand and inject global options.

    Args:
        cmd: The command to add.
        name: The name to register the command with.
        **kwargs: Additional arguments passed to the parent method.
    """
    # 1. Find global options attached to this group
    global_options = [
        param for param in getattr(self, "params", []) if getattr(param, "is_global", False)
    ]

    # 2. Inject them recursively into the subcommand and all its descendants
    if global_options:
        self._propagate_global_options(cmd, global_options)

    super().add_command(cmd, name, **kwargs)  # type: ignore

HandleResponseMixin

Auto-wraps commands added via command() or add_command() with returns_response when handle_response=True.

pyclif.HandleResponseMixin

Mixin that adds automatic Response dispatch to Click groups.

When handle_response_by_default is True (set via @app_group(handle_response=True)), every command registered through @group.command() or group.add_command() automatically wraps its return value: if the function returns a Response, it is printed using the output format stored in ctx.meta['pyclif.output_format'].

The per-command handle_response kwarg always takes precedence over the group-level default.

Source code in src/pyclif/core/mixins/response.py
class HandleResponseMixin:
    """Mixin that adds automatic Response dispatch to Click groups.

    When handle_response_by_default is True (set via
    @app_group(handle_response=True)), every command registered through
    @group.command() or group.add_command() automatically wraps its return
    value: if the function returns a Response, it is printed using the output
    format stored in ctx.meta['pyclif.output_format'].

    The per-command handle_response kwarg always takes precedence over the
    group-level default.
    """

    handle_response_by_default: bool = False

    def command(self, *args, handle_response=None, **kwargs):
        """Override Click.Group.command to automatically handle Response objects."""
        if handle_response is None:
            handle_response = self.handle_response_by_default
        # noinspection PyUnresolvedReferences
        decorator = super().command(*args, **kwargs)
        if not handle_response:
            # Mark the raw function as decided *before* calling decorator(f)
            # so that our add_command() override sees the flag when Click
            # calls self.add_command() internally during decorator(f).
            def wrapped_noop(f):
                """Pass-through decorator that marks the command as opted out."""
                setattr(f, _PYCLIF_RESPONSE_DECIDED, True)
                return decorator(f)

            return wrapped_noop

        def wrapped(f):
            """Wrapper for commands that automatically handles Response objects."""
            from pyclif.core.decorators import returns_response

            wrapped_f = returns_response(f)
            setattr(wrapped_f, _PYCLIF_RESPONSE_DECIDED, True)
            return decorator(wrapped_f)

        return wrapped

    def add_command(self, cmd, name=None):
        """Override Click.Group.add_command to propagate response handling.

        When handle_response_by_default is True:
        - Sub-groups: handle_response_by_default is propagated recursively so
          that all already-registered leaf commands are wrapped and future
          registrations on those groups are also covered.
        - Leaf commands: the callback is wrapped with returns_response directly.

        Commands whose callback carries the _PYCLIF_RESPONSE_DECIDED flag
        (set by the @group.command() factory path before Click registers the
        command) are left untouched so that explicit handle_response=False
        overrides and already-wrapped functions are both respected.
        """
        if self.handle_response_by_default:
            is_group = getattr(cmd, "commands", None) is not None
            if is_group:
                _log.debug(
                    "HandleResponseMixin.add_command: propagating handle_response "
                    "into sub-group '%s'",
                    cmd.name,
                )
                _apply_handle_response_to_group(cmd)
            else:
                already_decided = cmd.callback is not None and getattr(
                    cmd.callback, _PYCLIF_RESPONSE_DECIDED, False
                )
                if cmd.callback is not None and not already_decided:
                    from pyclif.core.decorators import returns_response

                    _log.debug(
                        "HandleResponseMixin.add_command: wrapping '%s' with returns_response",
                        cmd.name,
                    )
                    cmd.callback = returns_response(cmd.callback)
                else:
                    _log.debug(
                        "HandleResponseMixin.add_command: skipping leaf '%s' (already_decided=%s)",
                        cmd.name,
                        already_decided,
                    )
        else:
            _log.debug(
                "HandleResponseMixin.add_command: handle_response_by_default=False, skipping '%s'",
                cmd.name,
            )
        # noinspection PyUnresolvedReferences
        super().add_command(cmd, name=name)

command(*args, handle_response=None, **kwargs)

Override Click.Group.command to automatically handle Response objects.

Source code in src/pyclif/core/mixins/response.py
def command(self, *args, handle_response=None, **kwargs):
    """Override Click.Group.command to automatically handle Response objects."""
    if handle_response is None:
        handle_response = self.handle_response_by_default
    # noinspection PyUnresolvedReferences
    decorator = super().command(*args, **kwargs)
    if not handle_response:
        # Mark the raw function as decided *before* calling decorator(f)
        # so that our add_command() override sees the flag when Click
        # calls self.add_command() internally during decorator(f).
        def wrapped_noop(f):
            """Pass-through decorator that marks the command as opted out."""
            setattr(f, _PYCLIF_RESPONSE_DECIDED, True)
            return decorator(f)

        return wrapped_noop

    def wrapped(f):
        """Wrapper for commands that automatically handles Response objects."""
        from pyclif.core.decorators import returns_response

        wrapped_f = returns_response(f)
        setattr(wrapped_f, _PYCLIF_RESPONSE_DECIDED, True)
        return decorator(wrapped_f)

    return wrapped

add_command(cmd, name=None)

Override Click.Group.add_command to propagate response handling.

When handle_response_by_default is True: - Sub-groups: handle_response_by_default is propagated recursively so that all already-registered leaf commands are wrapped and future registrations on those groups are also covered. - Leaf commands: the callback is wrapped with returns_response directly.

Commands whose callback carries the _PYCLIF_RESPONSE_DECIDED flag (set by the @group.command() factory path before Click registers the command) are left untouched so that explicit handle_response=False overrides and already-wrapped functions are both respected.

Source code in src/pyclif/core/mixins/response.py
def add_command(self, cmd, name=None):
    """Override Click.Group.add_command to propagate response handling.

    When handle_response_by_default is True:
    - Sub-groups: handle_response_by_default is propagated recursively so
      that all already-registered leaf commands are wrapped and future
      registrations on those groups are also covered.
    - Leaf commands: the callback is wrapped with returns_response directly.

    Commands whose callback carries the _PYCLIF_RESPONSE_DECIDED flag
    (set by the @group.command() factory path before Click registers the
    command) are left untouched so that explicit handle_response=False
    overrides and already-wrapped functions are both respected.
    """
    if self.handle_response_by_default:
        is_group = getattr(cmd, "commands", None) is not None
        if is_group:
            _log.debug(
                "HandleResponseMixin.add_command: propagating handle_response "
                "into sub-group '%s'",
                cmd.name,
            )
            _apply_handle_response_to_group(cmd)
        else:
            already_decided = cmd.callback is not None and getattr(
                cmd.callback, _PYCLIF_RESPONSE_DECIDED, False
            )
            if cmd.callback is not None and not already_decided:
                from pyclif.core.decorators import returns_response

                _log.debug(
                    "HandleResponseMixin.add_command: wrapping '%s' with returns_response",
                    cmd.name,
                )
                cmd.callback = returns_response(cmd.callback)
            else:
                _log.debug(
                    "HandleResponseMixin.add_command: skipping leaf '%s' (already_decided=%s)",
                    cmd.name,
                    already_decided,
                )
    else:
        _log.debug(
            "HandleResponseMixin.add_command: handle_response_by_default=False, skipping '%s'",
            cmd.name,
        )
    # noinspection PyUnresolvedReferences
    super().add_command(cmd, name=name)

OutputFormatMixin

Dispatches Response objects to the appropriate formatter (JSON / YAML / Table / Rich / Raw) based on --output-format stored in ctx.meta.

pyclif.OutputFormatMixin

Provide methods for printing error messages and results based on a specified format.

This mixin expects the inheriting class to have 'console' and 'output_format' attributes. Every result must be a Response with a renderer attached.

Source code in src/pyclif/core/mixins/output.py
class OutputFormatMixin:
    """Provide methods for printing error messages and results based on a specified format.

    This mixin expects the inheriting class to have 'console' and 'output_format' attributes.
    Every result must be a Response with a renderer attached.
    """

    def print_error_based_on_format(self, exception: Exception) -> None:
        """Format and print an unhandled exception as a structured Response.

        Creates a single-result Response from the exception and dispatches
        it through the normal renderer path using _ExceptionRenderer.

        Args:
            exception: The exception to display.
        """
        result = OperationResult(
            success=False,
            item=type(exception).__name__,
            message=str(exception),
            error_code=1,
            data={"traceback": traceback.format_exc()},
        )
        response = Response.from_results(
            [result], message=str(exception), renderer=_ExceptionRenderer()
        )
        self.print_result_based_on_format(response)

    @staticmethod
    def _materialise_stream(response: Response) -> None:
        """Consume a streaming Response generator and re-evaluate its fields.

        Replaces data["stream"] with data["results"], then re-computes
        success, error_code, and message from the materialized list using
        the renderer's message methods.

        Args:
            response: A Response carrying a generator in data["stream"].
        """
        assert response.renderer is not None, "_materialise_stream requires a renderer"
        items = list(response.data.pop("stream"))
        failed = [r for r in items if not r.success]
        response.success = not bool(failed)
        response.error_code = failed[0].error_code if failed else None
        response.message = (
            response.renderer.get_success_message(items)
            if not failed
            else response.renderer.get_failure_message(items)
        )
        response.data["results"] = items

    def print_result_based_on_format(self, result: Response, options: dict | None = None) -> None:
        """Print a Response using its renderer.

        Streaming responses (data["stream"] present) are handled via the Live
        context for rich output, or materialized first for all other formats.

        Args:
            result: The Response to print. Must have a renderer attached.
            options: Optional dict with the filter_value key for --output-filter support.

        Raises:
            RuntimeError: When result.renderer is None — a programming error.
        """
        renderer: BaseRenderer = result.renderer or BaseRenderer()
        result.renderer = renderer

        opts: dict = options or {}
        output_format: str | None = getattr(self, "output_format", None)

        if "stream" in result.data:
            if output_format == "rich":
                renderable = renderer.rich_setup()
                items: list = []
                with Live(renderable, console=self.console):  # type: ignore[attr-defined]
                    for item in result.data.pop("stream"):
                        items.append(item)
                        renderer.rich_on_item(item, items)
                result.data["results"] = items
                renderer.rich_summary(result, self.console)  # type: ignore[attr-defined]
                return
            self._materialise_stream(result)
        filter_key: str | None = opts.get("filter_value")

        def _json() -> None:
            serialized = renderer.serialize(result)
            if filter_key:
                self._print_json(self._extract_filter_value(serialized, filter_key))
            else:
                self._print_json(serialized)

        def _yaml() -> None:
            serialized = renderer.serialize(result)
            if filter_key:
                self._print_yaml(self._extract_filter_value(serialized, filter_key))
            else:
                self._print_yaml(serialized)

        dispatch: dict[str, Any] = {
            "json": _json,
            "yaml": _yaml,
            "table": lambda: self.console.print(renderer.table(result)),  # type: ignore[attr-defined]
            "rich": lambda: renderer.rich(result, self.console),  # type: ignore[attr-defined]
            "raw": lambda: self._print_raw_dict(renderer.raw(result), filter_key),
            "text": lambda: self.console.print(renderer.text(result)),  # type: ignore[attr-defined]
        }
        dispatch.get(output_format or "table", dispatch["table"])()

    @staticmethod
    def _extract_filter_value(data: dict, filter_key: str) -> Any:
        """Extract a dotted-path key from a serialized response dict.

        Traverses data["data"] first (the structured payload), then falls back
        to the top-level response dict. Each segment of a dotted path is
        resolved in order; numeric segments are treated as list indices.

        Examples:
            "status"         -> data["data"]["status"]
            "results.0.id"   -> data["data"]["results"][0]["id"]
            "message"        -> data["message"]  (fallback when not in data)

        Args:
            data: Serialized response dict.
            filter_key: Dotted key path to extract (e.g. "results.0.id").

        Returns:
            The extracted value, or None when the path cannot be resolved.
        """
        _MISSING = object()
        segments = filter_key.split(".")

        def _traverse(obj: Any, segs: list[str]) -> Any:
            for seg in segs:
                if isinstance(obj, dict):
                    if seg not in obj:
                        return _MISSING
                    obj = obj[seg]
                elif isinstance(obj, list):
                    try:
                        obj = obj[int(seg)]
                    except (ValueError, IndexError):
                        return _MISSING
                else:
                    return _MISSING
            return obj

        sub = data.get("data")
        if isinstance(sub, dict):
            result = _traverse(sub, segments)
            if result is not _MISSING:
                return result

        result = _traverse(data, segments)
        return result if result is not _MISSING else None

    def _print_raw_dict(self, data: dict, filter_key: str | None) -> None:
        """Print a serialized dict as compact JSON, or extract and print a raw value.

        When filter_key is set, the extracted value is printed as-is with no
        re-serialization — suitable for shell scripting and piping.
        When filter_key is None, prints the full dict as compact JSON without
        syntax highlighting.

        Args:
            data: Serialized response dict from renderer.raw().
            filter_key: Key to extract, or None to print the full dict.
        """
        if filter_key:
            self.console.print(self._extract_filter_value(data, filter_key))  # type: ignore[attr-defined]
        else:
            self.console.print(json.dumps(data, cls=_FallbackEncoder))  # type: ignore[attr-defined]

    def _print_json(self, data: Any) -> None:
        """Print a value as syntax-highlighted JSON.

        Accepts any JSON-serializable value — dict, list, str, int, etc.
        Used for both full-response output and filtered single-value output.

        Args:
            data: Value to serialize and display as JSON.
        """
        self.console.print_json(json.dumps(data, cls=_FallbackEncoder))  # type: ignore[attr-defined]

    def _print_yaml(self, data: Any) -> None:
        """Print a value as syntax-highlighted YAML.

        Accepts any YAML-serializable value — dict, list, str, int, etc.
        Used for both full-response output and filtered single-value output.

        Args:
            data: Value to serialize and display as YAML.
        """
        yaml_content = yaml.dump(data, allow_unicode=True, indent=2, sort_keys=False)
        self.console.print(  # type: ignore[attr-defined]
            Syntax(yaml_content, "yaml", theme="ansi_dark"), soft_wrap=True
        )

print_error_based_on_format(exception)

Format and print an unhandled exception as a structured Response.

Creates a single-result Response from the exception and dispatches it through the normal renderer path using _ExceptionRenderer.

Parameters:

Name Type Description Default
exception Exception

The exception to display.

required
Source code in src/pyclif/core/mixins/output.py
def print_error_based_on_format(self, exception: Exception) -> None:
    """Format and print an unhandled exception as a structured Response.

    Creates a single-result Response from the exception and dispatches
    it through the normal renderer path using _ExceptionRenderer.

    Args:
        exception: The exception to display.
    """
    result = OperationResult(
        success=False,
        item=type(exception).__name__,
        message=str(exception),
        error_code=1,
        data={"traceback": traceback.format_exc()},
    )
    response = Response.from_results(
        [result], message=str(exception), renderer=_ExceptionRenderer()
    )
    self.print_result_based_on_format(response)

print_result_based_on_format(result, options=None)

Print a Response using its renderer.

Streaming responses (data["stream"] present) are handled via the Live context for rich output, or materialized first for all other formats.

Parameters:

Name Type Description Default
result Response

The Response to print. Must have a renderer attached.

required
options dict | None

Optional dict with the filter_value key for --output-filter support.

None

Raises:

Type Description
RuntimeError

When result.renderer is None — a programming error.

Source code in src/pyclif/core/mixins/output.py
def print_result_based_on_format(self, result: Response, options: dict | None = None) -> None:
    """Print a Response using its renderer.

    Streaming responses (data["stream"] present) are handled via the Live
    context for rich output, or materialized first for all other formats.

    Args:
        result: The Response to print. Must have a renderer attached.
        options: Optional dict with the filter_value key for --output-filter support.

    Raises:
        RuntimeError: When result.renderer is None — a programming error.
    """
    renderer: BaseRenderer = result.renderer or BaseRenderer()
    result.renderer = renderer

    opts: dict = options or {}
    output_format: str | None = getattr(self, "output_format", None)

    if "stream" in result.data:
        if output_format == "rich":
            renderable = renderer.rich_setup()
            items: list = []
            with Live(renderable, console=self.console):  # type: ignore[attr-defined]
                for item in result.data.pop("stream"):
                    items.append(item)
                    renderer.rich_on_item(item, items)
            result.data["results"] = items
            renderer.rich_summary(result, self.console)  # type: ignore[attr-defined]
            return
        self._materialise_stream(result)
    filter_key: str | None = opts.get("filter_value")

    def _json() -> None:
        serialized = renderer.serialize(result)
        if filter_key:
            self._print_json(self._extract_filter_value(serialized, filter_key))
        else:
            self._print_json(serialized)

    def _yaml() -> None:
        serialized = renderer.serialize(result)
        if filter_key:
            self._print_yaml(self._extract_filter_value(serialized, filter_key))
        else:
            self._print_yaml(serialized)

    dispatch: dict[str, Any] = {
        "json": _json,
        "yaml": _yaml,
        "table": lambda: self.console.print(renderer.table(result)),  # type: ignore[attr-defined]
        "rich": lambda: renderer.rich(result, self.console),  # type: ignore[attr-defined]
        "raw": lambda: self._print_raw_dict(renderer.raw(result), filter_key),
        "text": lambda: self.console.print(renderer.text(result)),  # type: ignore[attr-defined]
    }
    dispatch.get(output_format or "table", dispatch["table"])()

RichHelpersMixin

Rich console helpers available on the context: panels, rules, status spinners, prompts.

pyclif.RichHelpersMixin

Provide rich console helper methods for CLI contexts.

This mixin expects the inheriting class to have a 'console' attribute instantiated with a rich.console.Console object.

Source code in src/pyclif/core/mixins/rich.py
class RichHelpersMixin:
    """Provide rich console helper methods for CLI contexts.

    This mixin expects the inheriting class to have a 'console' attribute
    instantiated with a rich.console.Console object.
    """

    console: Console

    def rich_panel(
        self,
        text: Any,
        title: str | None = None,
        border_style: str | None = None,
        padding: tuple = (0, 1),
        console_print: bool = False,
        fit: bool = False,
    ) -> Panel:
        """Create a rich panel with the given formatting options.

        Args:
            text: The text content or renderable of the panel.
            title: The title of the panel.
            border_style: The border style of the panel.
            padding: The padding of the panel (top/bottom, left/right).
            console_print: If True, prints the panel directly to the console.
            fit: If True, the panel fits its contents.

        Returns:
            The rendered rich panel object.
        """
        panel_class = Panel.fit if fit else Panel
        panel_kwargs: dict[str, Any] = {"renderable": text, "title": title, "padding": padding}
        if border_style is not None:
            panel_kwargs["border_style"] = border_style
        renderer = panel_class(**panel_kwargs)
        if console_print:
            self.console.print(renderer)  # type: ignore
        return renderer

    def display_rule(self, title: str = "", style: str = "blue") -> None:
        """Display a horizontal rule with an optional title.

        Args:
            title (str): The text to display in the middle of the rule.
            style (str): The color and style of the rule.
        """
        self.console.print(Rule(title=title, style=style))  # type: ignore

    def show_status(self, message: str = "Processing...", spinner: str = "dots") -> Status:
        """Return a rich status context manager for long-running tasks.

        Args:
            message (str): The message to display next to the spinner.
            spinner (str): The type of spinner animation to use.

        Returns:
            Status: A context manager for the status.
        """
        return self.console.status(message, spinner=spinner)  # type: ignore

    def ask_user(
        self,
        question: str,
        default: str | None = None,
        choices: list[str] | None = None,
        password: bool = False,
    ) -> Any:
        """Prompt the user for input.

        Args:
            question: The prompt text to display.
            default: The default value if the user presses enter.
            choices: A list of valid choices to restrict input.
            password: If True, hides the user's input.

        Returns:
            The user's input.
        """
        return Prompt.ask(
            question, default=default, choices=choices, password=password, console=self.console
        )  # type: ignore

    def ask_confirmation(self, question: str, default: bool = False) -> bool:
        """Ask the user a yes or no confirmation question.

        Args:
            question (str): The prompt text to display.
            default (bool): The default answer if the user presses enter.

        Returns:
            bool: True if the user confirmed, False otherwise.
        """
        return Confirm.ask(question, default=default, console=self.console)  # type: ignore

rich_panel(text, title=None, border_style=None, padding=(0, 1), console_print=False, fit=False)

Create a rich panel with the given formatting options.

Parameters:

Name Type Description Default
text Any

The text content or renderable of the panel.

required
title str | None

The title of the panel.

None
border_style str | None

The border style of the panel.

None
padding tuple

The padding of the panel (top/bottom, left/right).

(0, 1)
console_print bool

If True, prints the panel directly to the console.

False
fit bool

If True, the panel fits its contents.

False

Returns:

Type Description
Panel

The rendered rich panel object.

Source code in src/pyclif/core/mixins/rich.py
def rich_panel(
    self,
    text: Any,
    title: str | None = None,
    border_style: str | None = None,
    padding: tuple = (0, 1),
    console_print: bool = False,
    fit: bool = False,
) -> Panel:
    """Create a rich panel with the given formatting options.

    Args:
        text: The text content or renderable of the panel.
        title: The title of the panel.
        border_style: The border style of the panel.
        padding: The padding of the panel (top/bottom, left/right).
        console_print: If True, prints the panel directly to the console.
        fit: If True, the panel fits its contents.

    Returns:
        The rendered rich panel object.
    """
    panel_class = Panel.fit if fit else Panel
    panel_kwargs: dict[str, Any] = {"renderable": text, "title": title, "padding": padding}
    if border_style is not None:
        panel_kwargs["border_style"] = border_style
    renderer = panel_class(**panel_kwargs)
    if console_print:
        self.console.print(renderer)  # type: ignore
    return renderer

display_rule(title='', style='blue')

Display a horizontal rule with an optional title.

Parameters:

Name Type Description Default
title str

The text to display in the middle of the rule.

''
style str

The color and style of the rule.

'blue'
Source code in src/pyclif/core/mixins/rich.py
def display_rule(self, title: str = "", style: str = "blue") -> None:
    """Display a horizontal rule with an optional title.

    Args:
        title (str): The text to display in the middle of the rule.
        style (str): The color and style of the rule.
    """
    self.console.print(Rule(title=title, style=style))  # type: ignore

show_status(message='Processing...', spinner='dots')

Return a rich status context manager for long-running tasks.

Parameters:

Name Type Description Default
message str

The message to display next to the spinner.

'Processing...'
spinner str

The type of spinner animation to use.

'dots'

Returns:

Name Type Description
Status Status

A context manager for the status.

Source code in src/pyclif/core/mixins/rich.py
def show_status(self, message: str = "Processing...", spinner: str = "dots") -> Status:
    """Return a rich status context manager for long-running tasks.

    Args:
        message (str): The message to display next to the spinner.
        spinner (str): The type of spinner animation to use.

    Returns:
        Status: A context manager for the status.
    """
    return self.console.status(message, spinner=spinner)  # type: ignore

ask_user(question, default=None, choices=None, password=False)

Prompt the user for input.

Parameters:

Name Type Description Default
question str

The prompt text to display.

required
default str | None

The default value if the user presses enter.

None
choices list[str] | None

A list of valid choices to restrict input.

None
password bool

If True, hides the user's input.

False

Returns:

Type Description
Any

The user's input.

Source code in src/pyclif/core/mixins/rich.py
def ask_user(
    self,
    question: str,
    default: str | None = None,
    choices: list[str] | None = None,
    password: bool = False,
) -> Any:
    """Prompt the user for input.

    Args:
        question: The prompt text to display.
        default: The default value if the user presses enter.
        choices: A list of valid choices to restrict input.
        password: If True, hides the user's input.

    Returns:
        The user's input.
    """
    return Prompt.ask(
        question, default=default, choices=choices, password=password, console=self.console
    )  # type: ignore

ask_confirmation(question, default=False)

Ask the user a yes or no confirmation question.

Parameters:

Name Type Description Default
question str

The prompt text to display.

required
default bool

The default answer if the user presses enter.

False

Returns:

Name Type Description
bool bool

True if the user confirmed, False otherwise.

Source code in src/pyclif/core/mixins/rich.py
def ask_confirmation(self, question: str, default: bool = False) -> bool:
    """Ask the user a yes or no confirmation question.

    Args:
        question (str): The prompt text to display.
        default (bool): The default answer if the user presses enter.

    Returns:
        bool: True if the user confirmed, False otherwise.
    """
    return Confirm.ask(question, default=default, console=self.console)  # type: ignore