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

1""" 

2Silent output handler. 

3 

4This implementation suppresses all output, useful for testing and 

5background operations where no user feedback is needed. 

6""" 

7 

8from collections.abc import Iterable 

9from typing import Any 

10 

11from frappe_manager.output_manager.base import OutputHandler 

12 

13 

14class SilentOutputHandler(OutputHandler): 

15 """ 

16 Output handler that suppresses all output. 

17 

18 This handler is useful for: 

19 - Unit testing business logic without output 

20 - Background operations 

21 - Automated scripts 

22 - Performance testing 

23 

24 All methods are no-ops except error() which still raises exceptions. 

25 """ 

26 

27 def __init__(self, verbose: bool = False): 

28 """ 

29 Initialize silent handler (verbose parameter is ignored). 

30 

31 Args: 

32 verbose: Ignored (always silent) 

33 """ 

34 super().__init__(verbose) 

35 

36 def start(self, text: str) -> None: 

37 """ 

38 Start a new operation (no-op). 

39 

40 Args: 

41 text: The initial status message (ignored) 

42 """ 

43 super().start(text) 

44 

45 def change_head(self, text: str, style: str | None = None) -> None: 

46 """ 

47 Update the current operation status message (no-op). 

48 

49 Args: 

50 text: The new status message (ignored) 

51 style: Optional style hint (ignored) 

52 """ 

53 

54 def update_head(self, text: str) -> None: 

55 """ 

56 Update the head text (no-op). 

57 

58 Args: 

59 text: The new head text (ignored) 

60 """ 

61 

62 def stop(self) -> None: 

63 """ 

64 Stop the current operation status display (no-op). 

65 """ 

66 super().stop() 

67 

68 def print(self, text: str, emoji_code: str = ":zap:", prefix: str | None = None, **kwargs) -> None: 

69 """ 

70 Print a message (no-op). 

71 

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

78 

79 def debug(self, text: str, emoji_code: str = ":bug:", **kwargs) -> None: 

80 """ 

81 Debug message (no-op). 

82 

83 Args: 

84 text: Debug message (ignored) 

85 emoji_code: Emoji code (ignored) 

86 **kwargs: Additional arguments (ignored) 

87 """ 

88 

89 def info(self, text: str, emoji_code: str = ":information:", **kwargs) -> None: 

90 """ 

91 Info message (no-op). 

92 

93 Args: 

94 text: Info message (ignored) 

95 emoji_code: Emoji code (ignored) 

96 **kwargs: Additional arguments (ignored) 

97 """ 

98 

99 def display_error(self, text: str, emoji_code: str = ":no_entry:") -> None: 

100 """ 

101 Display error (no-op). 

102 

103 Args: 

104 text: Error message (ignored) 

105 emoji_code: Emoji code (ignored) 

106 """ 

107 

108 def error(self, text: str, exception: Exception, emoji_code: str = ":no_entry:") -> None: 

109 """ 

110 Display an error message and raise the exception. 

111 

112 This method always raises the provided exception (no-op for display). 

113 

114 Args: 

115 text: The error message (ignored) 

116 exception: Exception to raise (required) 

117 emoji_code: Emoji code (ignored) 

118 

119 Raises: 

120 Exception: Always raises the provided exception 

121 """ 

122 raise exception 

123 

124 def warning(self, text: str, emoji_code: str = ":warning:") -> None: 

125 """ 

126 Display a warning message (no-op). 

127 

128 Args: 

129 text: The warning message (ignored) 

130 emoji_code: Emoji code (ignored) 

131 """ 

132 

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

145 

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 

163 

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

167 

168 Args: 

169 renderable: Content to display (ignored) 

170 padding: Padding (ignored) 

171 """ 

172 

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 

183 

184 if force_yes: 

185 return "yes" 

186 

187 if required_flag: 

188 raise NonInteractiveError( 

189 f"Cannot prompt in non-interactive mode: {prompt}", 

190 suggestions=[f"Provide: {required_flag}"], 

191 ) 

192 

193 if default is not None: 

194 return default 

195 

196 raise NonInteractiveError( 

197 f"Cannot prompt in non-interactive mode: {prompt}", 

198 suggestions=["Run without --non-interactive to enable prompts"], 

199 ) 

200 

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 

210 

211 if required_flag: 

212 raise NonInteractiveError( 

213 f"Cannot prompt in non-interactive mode: {prompt}", 

214 suggestions=[f"Provide: {required_flag}"], 

215 ) 

216 

217 if default is not None: 

218 return default 

219 

220 raise NonInteractiveError( 

221 f"Cannot prompt in non-interactive mode: {prompt}", 

222 suggestions=["Run without --non-interactive to enable prompts"], 

223 ) 

224 

225 @property 

226 def should_stream_docker(self) -> bool: 

227 return False 

228 

229 def print_data(self, data: Any, **kwargs) -> None: 

230 pass 

231 

232 def print_status(self, text: str, emoji_code: str = ":zap:", **kwargs) -> None: 

233 pass