Skip to content

Context

BaseContext

The context object passed to every command. Composites RichHelpersMixin and OutputFormatMixin to expose Rich console helpers and output dispatching on a single object.

Every pyclifer project subclasses BaseContext to attach project-specific state (database connections, API clients, config, …). Commands receive an instance of the subclass via the pass_*_context decorator generated by make_pass_decorator.

pyclifer.BaseContext

Bases: RichHelpersMixin, OutputFormatMixin

BaseContext class initializes state and combines output and rich helpers for CLI commands.

Source code in src/pyclifer/core/context.py
class BaseContext(RichHelpersMixin, OutputFormatMixin):
    """BaseContext class initializes state and combines output and rich helpers for CLI commands."""

    def __init__(self) -> None:
        """Initialize the context with a console and detect TTY mode."""
        self.console = Console()
        self.is_atty = sys.stdout.isatty()
        self.output_format = None

    @property
    def click(self) -> click_extra.Context:
        """Return the current Click context.

        Provides access to the full Click context (meta, params, info_name, etc.)
        from any BaseContext subclass without importing Click in command files.

        Returns:
            The active Click context for the running command.
        """
        return click_extra.get_current_context()

click property

Return the current Click context.

Provides access to the full Click context (meta, params, info_name, etc.) from any BaseContext subclass without importing Click in command files.

Returns:

Type Description
Context

The active Click context for the running command.

__init__()

Initialize the context with a console and detect TTY mode.

Source code in src/pyclifer/core/context.py
def __init__(self) -> None:
    """Initialize the context with a console and detect TTY mode."""
    self.console = Console()
    self.is_atty = sys.stdout.isatty()
    self.output_format = None

Subclassing

1. Define the subclass

Add project-specific attributes in __init__. Call super().__init__() to initialize the console and TTY detection from BaseContext.

# src/my_project/core/context.py
from pyclifer import BaseContext, make_pass_decorator

from my_project.core.integrations.db import Database


class MyContext(BaseContext):
    """Project context carrying the active Database connection."""

    def __init__(self) -> None:
        super().__init__()
        self._db: Database | None = None

    @property
    def db(self) -> Database:
        """Return the lazily initialized Database instance."""
        if self._db is None:
            self._db = Database()
        return self._db


pass_my_context = make_pass_decorator(MyContext, ensure=True)

ensure=True tells Click to create a MyContext instance automatically if one is not already on the context stack — commands never need to check for None.

2. Wire the pass decorator in cli.py

# src/my_project/cli.py
from pyclifer import app_group
from click_extra import pass_context

from my_project.core.context import MyContext, pass_my_context


@app_group(handle_response=True)
@pass_context
def cli(ctx):
    """My CLI application."""
    ctx.obj = MyContext()

3. Use in commands

# src/my_project/apps/articles/commands/list.py
from pyclifer import Response, command

from my_project.core.context import pass_my_context
from my_project.apps.articles.interfaces import ArticleInterface


@command()
@pass_my_context
def list_articles(ctx: MyContext) -> Response:
    """List all articles."""
    return ArticleInterface(ctx).respond("list_articles")

Rich helpers

All methods below are available on every BaseContext subclass via RichHelpersMixin.

rich_panel

Creates a Rich Panel. Pass console_print=True to print it immediately, or use the return value to embed it in a layout.

ctx.rich_panel("Operation complete.", title="Success", border_style="green", console_print=True)

display_rule

Prints a horizontal rule — useful to visually separate output sections.

ctx.display_rule("Results", style="blue")

show_status

Returns a Rich Status context manager for long-running operations.

with ctx.show_status("Fetching articles…"):
    articles = api.fetch_all()

ask_user

Prompts the user for text input.

name = ctx.ask_user("Enter your name", default="anonymous")
token = ctx.ask_user("API token", password=True)
role = ctx.ask_user("Role", choices=["admin", "editor", "viewer"])

ask_confirmation

Asks a yes/no question. Returns True on confirmation.

if ctx.ask_confirmation("Delete all records?", default=False):
    db.delete_all()

Key attributes

Attribute Type Description
console rich.console.Console Rich console used for all output
is_atty bool True when stdout is a TTY (interactive terminal)
output_format str \| None Active --output-format value; None defaults to table
click click_extra.Context The active Click context (access to meta, params, info_name, …)

ctx.click.meta is the shared dict where pyclifer stores runtime state (pyclifer.output_format, pyclifer.page, pyclifer.limit, …).


Output dispatching

OutputFormatMixin.print_result_based_on_format(response) is called automatically by returns_response — commands never call it directly. It is exposed on BaseContext so custom renderers and advanced commands can trigger it manually when needed.

pyclifer.OutputFormatMixin.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/pyclifer/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._apply_output_filter(serialized, filter_key))
        else:
            self._print_json(serialized)

    def _yaml() -> None:
        serialized = renderer.serialize(result)
        if filter_key:
            self._print_yaml(self._apply_output_filter(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"])()