Coverage for typer / _click / termui.py: 100%

138 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-09 00:15 +0000

1import io 1aebfcdg

2from collections.abc import Callable, Iterable 1aebfcdg

3from contextlib import AbstractContextManager 1aebfcdg

4from typing import IO, TYPE_CHECKING, Any, AnyStr, TextIO, TypeVar, overload 1aebfcdg

5 

6from ..exceptions import Abort 1aebfcdg

7from .exceptions import UsageError 1aebfcdg

8from .globals import resolve_color_default 1aebfcdg

9from .types import ParamType, convert_type 1aebfcdg

10from .utils import LazyFile, echo 1aebfcdg

11 

12if TYPE_CHECKING: 1aebfcdg

13 from ._termui_impl import ProgressBar 

14 

15V = TypeVar("V") 1aebfcdg

16 

17# The prompt functions to use. The doc tools currently override these 

18# functions to customize how they work. 

19visible_prompt_func: Callable[[str], str] = input 1aebfcdg

20 

21_ansi_colors = { 1aebfcdg

22 "black": 30, 

23 "red": 31, 

24 "green": 32, 

25 "yellow": 33, 

26 "blue": 34, 

27 "magenta": 35, 

28 "cyan": 36, 

29 "white": 37, 

30 "reset": 39, 

31 "bright_black": 90, 

32 "bright_red": 91, 

33 "bright_green": 92, 

34 "bright_yellow": 93, 

35 "bright_blue": 94, 

36 "bright_magenta": 95, 

37 "bright_cyan": 96, 

38 "bright_white": 97, 

39} 

40_ansi_reset_all = "\033[0m" 1aebfcdg

41 

42 

43def hidden_prompt_func(prompt: str) -> str: 1aebfcdg

44 import getpass 1aebfcdg

45 

46 return getpass.getpass(prompt) 1aebfcdg

47 

48 

49def _build_prompt( 1aebfcdg

50 text: str, 

51 suffix: str, 

52 show_default: bool = False, 

53 default: Any | None = None, 

54 show_choices: bool = True, 

55 type: ParamType | None = None, 

56) -> str: 

57 # prevent circular imports 

58 from .._types import TyperChoice 1aebfcdg

59 

60 prompt = text 1aebfcdg

61 if type is not None and show_choices and isinstance(type, TyperChoice): 1aebfcdg

62 prompt += f" ({', '.join(map(str, type.choices))})" 1aebfcdg

63 if default is not None and show_default: 1aebfcdg

64 prompt = f"{prompt} [{_format_default(default)}]" 1aebfcdg

65 return f"{prompt}{suffix}" 1aebfcdg

66 

67 

68def _format_default(default: Any) -> Any: 1aebfcdg

69 if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): 1aebfcdg

70 return default.name 1aebfcdg

71 

72 return default 1aebfcdg

73 

74 

75def prompt( 1aebfcdg

76 text: str, 

77 default: Any | None = None, 

78 hide_input: bool = False, 

79 confirmation_prompt: bool | str = False, 

80 type: ParamType | Any | None = None, 

81 value_proc: Callable[[str], Any] | None = None, 

82 prompt_suffix: str = ": ", 

83 show_default: bool = True, 

84 err: bool = False, 

85 show_choices: bool = True, 

86) -> Any: 

87 """Prompts a user for input. This is a convenience function that can 

88 be used to prompt a user for input later. 

89 

90 If the user aborts the input by sending an interrupt signal, this 

91 function will catch it and raise an `Abort` exception. 

92 """ 

93 

94 def prompt_func(text: str) -> str: 1aebfcdg

95 f = hidden_prompt_func if hide_input else visible_prompt_func 1aebfcdg

96 try: 1aebfcdg

97 # Write the prompt separately so that we get nice 

98 # coloring through colorama on Windows 

99 echo(text[:-1], nl=False, err=err) 1aebfcdg

100 # Echo the last character to stdout to work around an issue where 

101 # readline causes backspace to clear the whole line. 

102 return f(text[-1:]) 1aebfcdg

103 except (KeyboardInterrupt, EOFError): # pragma: no cover 

104 # getpass doesn't print a newline if the user aborts input with ^C. 

105 # Allegedly this behavior is inherited from getpass(3). 

106 # A doc bug has been filed at https://bugs.python.org/issue24711 

107 if hide_input: 

108 echo(None, err=err) 

109 raise Abort() from None 

110 

111 if value_proc is None: 1aebfcdg

112 value_proc = convert_type(type, default) 1aebfcdg

113 

114 prompt = _build_prompt( 1aebfcdg

115 text, prompt_suffix, show_default, default, show_choices, type 

116 ) 

117 

118 if confirmation_prompt: 1aebfcdg

119 if confirmation_prompt is True: 1aebfcdg

120 confirmation_prompt = "Repeat for confirmation" 1aebfcdg

121 

122 confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) 1aebfcdg

123 

124 while True: 1aebfcdg

125 while True: 1aebfcdg

126 value = prompt_func(prompt) 1aebfcdg

127 if value: 1aebfcdg

128 break 1aebfcdg

129 elif default is not None: 1aebfcdg

130 value = default 1aebfcdg

131 break 1aebfcdg

132 try: 1aebfcdg

133 result = value_proc(value) 1aebfcdg

134 except UsageError as e: # pragma: no cover 

135 if hide_input: 

136 echo("Error: The value you entered was invalid.", err=err) 

137 else: 

138 echo(f"Error: {e.message}", err=err) 

139 continue 

140 if not confirmation_prompt: 1aebfcdg

141 return result 1aebfcdg

142 while True: 1aebfcdg

143 value2 = prompt_func(confirmation_prompt) 1aebfcdg

144 is_empty = not value and not value2 1aebfcdg

145 if value2 or is_empty: 1aebfcdg

146 break 1aebfcdg

147 if value == value2: 1aebfcdg

148 return result 1aebfcdg

149 echo("Error: The two entered values do not match.", err=err) 1aebfcdg

150 

151 

152def confirm( 1aebfcdg

153 text: str, 

154 default: bool | None = False, 

155 abort: bool = False, 

156 prompt_suffix: str = ": ", 

157 show_default: bool = True, 

158 err: bool = False, 

159) -> bool: 

160 """Prompts for confirmation (yes/no question). 

161 

162 If the user aborts the input by sending a interrupt signal this 

163 function will catch it and raise an `Abort` exception. 

164 """ 

165 prompt = _build_prompt( 1aebfcdg

166 text, 

167 prompt_suffix, 

168 show_default, 

169 "y/n" if default is None else ("Y/n" if default else "y/N"), 

170 ) 

171 

172 while True: 1aebfcdg

173 try: 1aebfcdg

174 # Write the prompt separately so that we get nice 

175 # coloring through colorama on Windows 

176 echo(prompt[:-1], nl=False, err=err) 1aebfcdg

177 # Echo the last character to stdout to work around an issue where 

178 # readline causes backspace to clear the whole line. 

179 value = visible_prompt_func(prompt[-1:]).lower().strip() 1aebfcdg

180 except (KeyboardInterrupt, EOFError): # pragma: no cover 

181 raise Abort() from None 

182 if value in ("y", "yes"): 1aebfcdg

183 rv = True 1aebfcdg

184 elif value in ("n", "no"): 1aebfcdg

185 rv = False 1aebfcdg

186 elif default is not None and value == "": 1aebfcdg

187 rv = default 1aebfcdg

188 else: # pragma: no cover 

189 echo("Error: invalid input", err=err) 

190 continue 

191 break 1aebfcdg

192 if abort and not rv: 1aebfcdg

193 raise Abort() 1aebfcdg

194 return rv 1aebfcdg

195 

196 

197@overload 1aebfcdg

198def progressbar( 1aebfcdg

199 *, 

200 length: int, 1abcd

201 label: str | None = None, 1aebfcdg

202 hidden: bool = False, 1aebfcdg

203 show_eta: bool = True, 1aebfcdg

204 show_percent: bool | None = None, 1aebfcdg

205 show_pos: bool = False, 1aebfcdg

206 fill_char: str = "#", 1aebfcdg

207 empty_char: str = "-", 1aebfcdg

208 bar_template: str = "%(label)s [%(bar)s] %(info)s", 1aebfcdg

209 info_sep: str = " ", 1aebfcdg

210 width: int = 36, 1aebfcdg

211 file: TextIO | None = None, 1aebfcdg

212 color: bool | None = None, 1aebfcdg

213 update_min_steps: int = 1, 1aebfcdg

214) -> "ProgressBar[int]": ... 1abcd

215 

216 

217@overload 1aebfcdg

218def progressbar( 1aebfcdg

219 iterable: Iterable[V] | None = None, 1abcd

220 length: int | None = None, 1abcd

221 label: str | None = None, 1abcd

222 hidden: bool = False, 1abcd

223 show_eta: bool = True, 1abcd

224 show_percent: bool | None = None, 1abcd

225 show_pos: bool = False, 1abcd

226 item_show_func: Callable[[V | None], str | None] | None = None, 1abcd

227 fill_char: str = "#", 1abcd

228 empty_char: str = "-", 1abcd

229 bar_template: str = "%(label)s [%(bar)s] %(info)s", 1abcd

230 info_sep: str = " ", 1abcd

231 width: int = 36, 1abcd

232 file: TextIO | None = None, 1abcd

233 color: bool | None = None, 1abcd

234 update_min_steps: int = 1, 1abcd

235) -> "ProgressBar[V]": ... 1abcd

236 

237 

238def progressbar( 1aebfcdg

239 iterable: Iterable[V] | None = None, 

240 length: int | None = None, 

241 label: str | None = None, 

242 hidden: bool = False, 

243 show_eta: bool = True, 

244 show_percent: bool | None = None, 

245 show_pos: bool = False, 

246 item_show_func: Callable[[V | None], str | None] | None = None, 

247 fill_char: str = "#", 

248 empty_char: str = "-", 

249 bar_template: str = "%(label)s [%(bar)s] %(info)s", 

250 info_sep: str = " ", 

251 width: int = 36, 

252 file: TextIO | None = None, 

253 color: bool | None = None, 

254 update_min_steps: int = 1, 

255) -> "ProgressBar[V]": 

256 """This function creates an iterable context manager that can be used 

257 to iterate over something while showing a progress bar. It will 

258 either iterate over the `iterable` or `length` items (that are counted 

259 up). While iteration happens, this function will print a rendered 

260 progress bar to the given `file` (defaults to stdout) and will attempt 

261 to calculate remaining time and more. By default, this progress bar 

262 will not be rendered if the file is not a terminal. 

263 

264 The context manager creates the progress bar. When the context 

265 manager is entered the progress bar is already created. With every 

266 iteration over the progress bar, the iterable passed to the bar is 

267 advanced and the bar is updated. When the context manager exits, 

268 a newline is printed and the progress bar is finalized on screen. 

269 

270 Note: The progress bar is currently designed for use cases where the 

271 total progress can be expected to take at least several seconds. 

272 Because of this, the ProgressBar class object won't display 

273 progress that is considered too fast, and progress where the time 

274 between steps is less than a second. 

275 

276 No printing must happen or the progress bar will be unintentionally 

277 destroyed. 

278 """ 

279 from ._termui_impl import ProgressBar 1aebfcdg

280 

281 color = resolve_color_default(color) 1aebfcdg

282 return ProgressBar( 1aebfcdg

283 iterable=iterable, 

284 length=length, 

285 hidden=hidden, 

286 show_eta=show_eta, 

287 show_percent=show_percent, 

288 show_pos=show_pos, 

289 item_show_func=item_show_func, 

290 fill_char=fill_char, 

291 empty_char=empty_char, 

292 bar_template=bar_template, 

293 info_sep=info_sep, 

294 file=file, 

295 label=label, 

296 width=width, 

297 color=color, 

298 update_min_steps=update_min_steps, 

299 ) 

300 

301 

302def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str: 1aebfcdg

303 if isinstance(color, int): 1aebfcdg

304 return f"{38 + offset};5;{color:d}" 1aebfcdg

305 

306 if isinstance(color, (tuple, list)): 1aebfcdg

307 r, g, b = color 1aebfcdg

308 return f"{38 + offset};2;{r:d};{g:d};{b:d}" 1aebfcdg

309 

310 return str(_ansi_colors[color] + offset) 1aebfcdg

311 

312 

313def style( 1aebfcdg

314 text: Any, 

315 fg: int | tuple[int, int, int] | str | None = None, 

316 bg: int | tuple[int, int, int] | str | None = None, 

317 bold: bool | None = None, 

318 dim: bool | None = None, 

319 underline: bool | None = None, 

320 overline: bool | None = None, 

321 italic: bool | None = None, 

322 blink: bool | None = None, 

323 reverse: bool | None = None, 

324 strikethrough: bool | None = None, 

325 reset: bool = True, 

326) -> str: 

327 """Styles a text with ANSI styles and returns the new string. By 

328 default the styling is self contained which means that at the end 

329 of the string a reset code is issued. This can be prevented by 

330 passing ``reset=False``. 

331 """ 

332 if not isinstance(text, str): 1aebfcdg

333 text = str(text) 1aebfcdg

334 

335 bits = [] 1aebfcdg

336 

337 if fg: 1aebfcdg

338 try: 1aebfcdg

339 bits.append(f"\033[{_interpret_color(fg)}m") 1aebfcdg

340 except KeyError: 1aebfcdg

341 raise TypeError(f"Unknown color {fg!r}") from None 1aebfcdg

342 

343 if bg: 1aebfcdg

344 try: 1aebfcdg

345 bits.append(f"\033[{_interpret_color(bg, 10)}m") 1aebfcdg

346 except KeyError: 1aebfcdg

347 raise TypeError(f"Unknown color {bg!r}") from None 1aebfcdg

348 

349 if bold is not None: 1aebfcdg

350 bits.append(f"\033[{1 if bold else 22}m") 1aebfcdg

351 if dim is not None: 1aebfcdg

352 bits.append(f"\033[{2 if dim else 22}m") 1aebfcdg

353 if underline is not None: 1aebfcdg

354 bits.append(f"\033[{4 if underline else 24}m") 1aebfcdg

355 if overline is not None: 1aebfcdg

356 bits.append(f"\033[{53 if overline else 55}m") 1aebfcdg

357 if italic is not None: 1aebfcdg

358 bits.append(f"\033[{3 if italic else 23}m") 1aebfcdg

359 if blink is not None: 1aebfcdg

360 bits.append(f"\033[{5 if blink else 25}m") 1aebfcdg

361 if reverse is not None: 1aebfcdg

362 bits.append(f"\033[{7 if reverse else 27}m") 1aebfcdg

363 if strikethrough is not None: 1aebfcdg

364 bits.append(f"\033[{9 if strikethrough else 29}m") 1aebfcdg

365 bits.append(text) 1aebfcdg

366 if reset: 1aebfcdg

367 bits.append(_ansi_reset_all) 1aebfcdg

368 return "".join(bits) 1aebfcdg

369 

370 

371def secho( 1aebfcdg

372 message: Any | None = None, 

373 file: IO[AnyStr] | None = None, 

374 nl: bool = True, 

375 err: bool = False, 

376 color: bool | None = None, 

377 **styles: Any, 

378) -> None: 

379 """This function combines `echo` and `style` into one call.""" 

380 if message is not None and not isinstance(message, (bytes, bytearray)): 1aebfcdg

381 message = style(message, **styles) 1aebfcdg

382 

383 return echo(message, file=file, nl=nl, err=err, color=color) 1aebfcdg

384 

385 

386def launch(url: str, wait: bool = False, locate: bool = False) -> int: 1aebfcdg

387 """This function launches the given URL (or filename) in the default 

388 viewer application for this file type. If this is an executable, it 

389 might launch the executable in a new session. The return value is 

390 the exit code of the launched application. Usually, ``0`` indicates 

391 success. 

392 """ 

393 from ._termui_impl import open_url 1aebfcdg

394 

395 return open_url(url, wait=wait, locate=locate) 1aebfcdg

396 

397 

398# If this is provided, getchar() calls into this instead. This is used 

399# for unittesting purposes. 

400_getchar: Callable[[bool], str] | None = None 1aebfcdg

401 

402 

403def getchar(echo: bool = False) -> str: 1aebfcdg

404 """Fetches a single character from the terminal and returns it. This 

405 will always return a unicode character and under certain rare 

406 circumstances this might return more than one character. The 

407 situations which more than one character is returned is when for 

408 whatever reason multiple characters end up in the terminal buffer or 

409 standard input was not actually a terminal. 

410 

411 Note that this will always read from the terminal, even if something 

412 is piped into the standard input. 

413 

414 Note for Windows: in rare cases when typing non-ASCII characters, this 

415 function might wait for a second character and then return both at once. 

416 This is because certain Unicode characters look like special-key markers. 

417 """ 

418 global _getchar 

419 

420 if _getchar is None: 1aebfcdg

421 from ._termui_impl import getchar as f 1aebfcdg

422 

423 _getchar = f 1aebfcdg

424 

425 return _getchar(echo) 1aebfcdg

426 

427 

428def raw_terminal() -> AbstractContextManager[int]: 1aebfcdg

429 from ._termui_impl import raw_terminal as f 1aebfcdg

430 

431 return f() 1aebfcdg