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 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