Coverage for frappe_manager / output_manager / silent_output.py: 90%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
1"""
2Silent output handler.
4This implementation suppresses all output, useful for testing and
5background operations where no user feedback is needed.
6"""
8from collections.abc import Iterable
9from typing import Any
11from frappe_manager.output_manager.base import OutputHandler
14class SilentOutputHandler(OutputHandler):
15 """
16 Output handler that suppresses all output.
18 This handler is useful for:
19 - Unit testing business logic without output
20 - Background operations
21 - Automated scripts
22 - Performance testing
24 All methods are no-ops except error() which still raises exceptions.
25 """
27 def __init__(self, verbose: bool = False):
28 """
29 Initialize silent handler (verbose parameter is ignored).
31 Args:
32 verbose: Ignored (always silent)
33 """
34 super().__init__(verbose)
36 def start(self, text: str) -> None:
37 """
38 Start a new operation (no-op).
40 Args:
41 text: The initial status message (ignored)
42 """
43 super().start(text)
45 def change_head(self, text: str, style: str | None = None) -> None:
46 """
47 Update the current operation status message (no-op).
49 Args:
50 text: The new status message (ignored)
51 style: Optional style hint (ignored)
52 """
54 def update_head(self, text: str) -> None:
55 """
56 Update the head text (no-op).
58 Args:
59 text: The new head text (ignored)
60 """
62 def stop(self) -> None:
63 """
64 Stop the current operation status display (no-op).
65 """
66 super().stop()
68 def print(self, text: str, emoji_code: str = ":zap:", prefix: str | None = None, **kwargs) -> None:
69 """
70 Print a message (no-op).
72 Args:
73 text: The message to print (ignored)
74 emoji_code: Emoji code (ignored)
75 prefix: Optional prefix (ignored)
76 **kwargs: Additional arguments (ignored)
77 """
79 def debug(self, text: str, emoji_code: str = ":bug:", **kwargs) -> None:
80 """
81 Debug message (no-op).
83 Args:
84 text: Debug message (ignored)
85 emoji_code: Emoji code (ignored)
86 **kwargs: Additional arguments (ignored)
87 """
89 def info(self, text: str, emoji_code: str = ":information:", **kwargs) -> None:
90 """
91 Info message (no-op).
93 Args:
94 text: Info message (ignored)
95 emoji_code: Emoji code (ignored)
96 **kwargs: Additional arguments (ignored)
97 """
99 def display_error(self, text: str, emoji_code: str = ":no_entry:") -> None:
100 """
101 Display error (no-op).
103 Args:
104 text: Error message (ignored)
105 emoji_code: Emoji code (ignored)
106 """
108 def error(self, text: str, exception: Exception, emoji_code: str = ":no_entry:") -> None:
109 """
110 Display an error message and raise the exception.
112 This method always raises the provided exception (no-op for display).
114 Args:
115 text: The error message (ignored)
116 exception: Exception to raise (required)
117 emoji_code: Emoji code (ignored)
119 Raises:
120 Exception: Always raises the provided exception
121 """
122 raise exception
124 def warning(self, text: str, emoji_code: str = ":warning:") -> None:
125 """
126 Display a warning message (no-op).
128 Args:
129 text: The warning message (ignored)
130 emoji_code: Emoji code (ignored)
131 """
133 def live_lines(
134 self,
135 data: Iterable[tuple[str, bytes]],
136 stdout: bool = True,
137 stderr: bool = True,
138 lines: int = 4,
139 padding: tuple[int, int, int, int] = (0, 0, 0, 2),
140 stop_string: str | None = None,
141 log_prefix: str = "=>",
142 ) -> None:
143 """
144 Display live streaming output (consumes iterator but produces no output).
146 Args:
147 data: Iterator yielding (source, line) tuples
148 stdout: Whether to process stdout lines (ignored)
149 stderr: Whether to process stderr lines (ignored)
150 lines: Maximum number of lines (ignored)
151 padding: Padding (ignored)
152 stop_string: String that stops iteration when found
153 log_prefix: Prefix for each line (ignored)
154 """
155 for _source, line in data:
156 if stop_string:
157 try:
158 decoded_line = line.decode()
159 if stop_string.lower() in decoded_line.lower():
160 break
161 except Exception:
162 pass
164 def update_live(self, renderable: Any = None, padding: tuple[int, int, int, int] = (0, 0, 0, 0)) -> None:
165 """
166 Update the live display (no-op).
168 Args:
169 renderable: Content to display (ignored)
170 padding: Padding (ignored)
171 """
173 def prompt_ask(
174 self,
175 prompt: str = "",
176 choices: list | None = None,
177 default: str | None = None,
178 force_yes: bool = False,
179 required_flag: str | None = None,
180 **kwargs,
181 ) -> str:
182 from frappe_manager.exceptions import NonInteractiveError
184 if force_yes:
185 return "yes"
187 if required_flag:
188 raise NonInteractiveError(
189 f"Cannot prompt in non-interactive mode: {prompt}",
190 suggestions=[f"Provide: {required_flag}"],
191 )
193 if default is not None:
194 return default
196 raise NonInteractiveError(
197 f"Cannot prompt in non-interactive mode: {prompt}",
198 suggestions=["Run without --non-interactive to enable prompts"],
199 )
201 def prompt_fuzzy(
202 self,
203 prompt: str,
204 choices: list[str],
205 default: str | None = None,
206 required_flag: str | None = None,
207 **kwargs,
208 ) -> str:
209 from frappe_manager.exceptions import NonInteractiveError
211 if required_flag:
212 raise NonInteractiveError(
213 f"Cannot prompt in non-interactive mode: {prompt}",
214 suggestions=[f"Provide: {required_flag}"],
215 )
217 if default is not None:
218 return default
220 raise NonInteractiveError(
221 f"Cannot prompt in non-interactive mode: {prompt}",
222 suggestions=["Run without --non-interactive to enable prompts"],
223 )
225 @property
226 def should_stream_docker(self) -> bool:
227 return False
229 def print_data(self, data: Any, **kwargs) -> None:
230 pass
232 def print_status(self, text: str, emoji_code: str = ":zap:", **kwargs) -> None:
233 pass