Source code for thesis.core.output.modes

"""
Output mode configuration for the thesis framework.

Defines the verbosity levels, summary detail, and output format settings
that control what the user sees during and after a run.

Example:
    >>> from thesis.core.output.modes import OutputConfig, OutputMode
    >>> cfg = OutputConfig(mode=OutputMode.VERBOSE)
    >>> cfg.min_event_level
    EventLevel.DEBUG
"""

import os
import sys
from enum import Enum
from typing import Optional

from pydantic import BaseModel, ConfigDict, Field

from .events import EventLevel

__all__ = [
    "OutputMode",
    "SummaryDetail",
    "OutputFormat",
    "OutputConfig",
]


[docs] class OutputMode(str, Enum): """Verbosity level for console output. - QUIET: errors and final result only. - NORMAL: concise, human-friendly progress and key events. - VERBOSE: full informational logs, timing, debug context. """ QUIET = "quiet" NORMAL = "normal" VERBOSE = "verbose"
[docs] class SummaryDetail(str, Enum): """Level of detail in the end-of-run summary. - OFF: no summary printed. - COMPACT: one-line status + 3-6 key bullets. - FULL: expanded summary with all metadata. """ OFF = "off" COMPACT = "compact" FULL = "full"
[docs] class OutputFormat(str, Enum): """Output serialization format. - HUMAN: colored/styled text for interactive terminals. - JSON: machine-readable JSON lines (one object per event/summary). """ HUMAN = "human" JSON = "json"
[docs] class OutputConfig(BaseModel): """Configuration for the output system. Controls verbosity, summary detail, progress display, and format. CLI flags override values set here. Attributes: mode: Verbosity level. summary: Summary detail level. progress: Whether to show progress bars/spinners. ``None`` means auto-detect (enabled for TTY, disabled otherwise). output_format: Serialization format. """ model_config = ConfigDict(extra="forbid") mode: OutputMode = Field( default=OutputMode.NORMAL, description="Verbosity: quiet | normal | verbose", ) summary: SummaryDetail = Field( default=SummaryDetail.COMPACT, description="Summary detail: off | compact | full", ) progress: Optional[bool] = Field( default=None, description="Show progress UI (None = auto-detect TTY)", ) output_format: OutputFormat = Field( default=OutputFormat.HUMAN, description="Output format: human | json", ) # ------------------------------------------------------------------ # Derived helpers # ------------------------------------------------------------------ @property def min_event_level(self) -> EventLevel: """Minimum event level to display in the current mode.""" if self.mode == OutputMode.QUIET: return EventLevel.ERROR if self.mode == OutputMode.VERBOSE: return EventLevel.DEBUG # NORMAL return EventLevel.IMPORTANT @property def show_progress(self) -> bool: """Whether animated progress UI should be shown. Progress remains enabled across output modes unless the user explicitly disables it. The CLI and logging layers are responsible for routing non-progress output in a progress-safe way. """ if self.progress is not None: return self.progress return _is_interactive_tty() @property def is_verbose(self) -> bool: """Shorthand for checking verbose mode.""" return self.mode == OutputMode.VERBOSE @property def is_quiet(self) -> bool: """Shorthand for checking quiet mode.""" return self.mode == OutputMode.QUIET @property def is_json(self) -> bool: """Shorthand for checking JSON output format.""" return self.output_format == OutputFormat.JSON
def _is_interactive_tty() -> bool: """Detect whether stderr is an interactive terminal. Returns False when running in CI (``CI`` env var set), when output is piped, or when the ``NO_COLOR`` convention is in effect. """ if os.environ.get("CI"): return False if os.environ.get("NO_COLOR"): return False try: return sys.stderr.isatty() except AttributeError: return False