Coverage for / home / runner / work / bijux-cli / bijux-cli / src / bijux_cli / infra / emitter.py: 91%
41 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 17:59 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 17:59 +0000
1# SPDX-License-Identifier: Apache-2.0
2# Copyright © 2025 Bijan Mousavi
4"""Output emitter adapters."""
6from __future__ import annotations
8import sys
9from typing import Any
11import structlog
13from bijux_cli.core.enums import LogLevel, OutputFormat
14from bijux_cli.infra.serializer import serializer_for
17class ConsoleEmitter:
18 """Emitter that serializes and writes payloads to stdout."""
20 def __init__(
21 self,
22 telemetry: Any,
23 output_format: OutputFormat,
24 ) -> None:
25 """Initialize the console emitter."""
26 self._telemetry = telemetry
27 self._output_format = output_format
28 self._logger = structlog.get_logger(__name__)
30 def emit(
31 self,
32 payload: Any,
33 *,
34 fmt: OutputFormat,
35 pretty: bool = False,
36 level: LogLevel = LogLevel.INFO,
37 message: str = "Emitting output",
38 output: str | None = None,
39 emit_output: bool = True,
40 emit_diagnostics: bool = False,
41 **context: Any,
42 ) -> None:
43 """Serialize and emit a payload."""
44 if not emit_output:
45 return
47 output_format = fmt
48 serializer = serializer_for(output_format, self._telemetry)
49 try:
50 output_str = serializer.dumps(payload, fmt=output_format, pretty=pretty)
51 except Exception as error:
52 self._logger.error("Serialization failed", error=str(error), **context)
53 raise RuntimeError(f"Serialization failed: {error}") from error
55 stripped = output_str.rstrip("\n")
56 if output:
57 with open(output, "w", encoding="utf-8") as f:
58 f.write(stripped)
59 else:
60 print(stripped, file=sys.stdout, flush=True)
62 try:
63 format_name = output_format.value
64 self._telemetry.event(
65 "output_emitted",
66 {"format": format_name, "size_chars": len(stripped)},
67 )
68 except Exception as tel_err:
69 self._logger.debug("Telemetry failed", error=str(tel_err), **context)
71 def flush(self) -> None:
72 """Flushes standard output."""
73 sys.stdout.flush()
76class NullEmitter:
77 """Emitter that discards output."""
79 def emit(
80 self,
81 payload: Any,
82 *,
83 fmt: OutputFormat,
84 pretty: bool = False,
85 level: LogLevel = LogLevel.INFO,
86 message: str = "Emitting output",
87 output: str | None = None,
88 emit_output: bool = True,
89 emit_diagnostics: bool = False,
90 **context: Any,
91 ) -> None:
92 """Drop emitted payloads."""
93 _ = (
94 payload,
95 fmt,
96 pretty,
97 level,
98 message,
99 output,
100 emit_output,
101 emit_diagnostics,
102 )
103 _ = context
104 return None
106 def flush(self) -> None:
107 """No-op flush for the null emitter."""
108 return None
111__all__ = ["ConsoleEmitter", "NullEmitter"]