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
« 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
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
12if TYPE_CHECKING: 1aebfcdg
13 from ._termui_impl import ProgressBar
15V = TypeVar("V") 1aebfcdg
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
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
43def hidden_prompt_func(prompt: str) -> str: 1aebfcdg
44 import getpass 1aebfcdg
46 return getpass.getpass(prompt) 1aebfcdg
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
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
68def _format_default(default: Any) -> Any: 1aebfcdg
69 if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): 1aebfcdg
70 return default.name 1aebfcdg
72 return default 1aebfcdg
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.
90 If the user aborts the input by sending an interrupt signal, this
91 function will catch it and raise an `Abort` exception.
92 """
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
111 if value_proc is None: 1aebfcdg
112 value_proc = convert_type(type, default) 1aebfcdg
114 prompt = _build_prompt( 1aebfcdg
115 text, prompt_suffix, show_default, default, show_choices, type
116 )
118 if confirmation_prompt: 1aebfcdg
119 if confirmation_prompt is True: 1aebfcdg
120 confirmation_prompt = "Repeat for confirmation" 1aebfcdg
122 confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) 1aebfcdg
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
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).
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 )
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
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
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
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.
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.
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.
276 No printing must happen or the progress bar will be unintentionally
277 destroyed.
278 """
279 from ._termui_impl import ProgressBar 1aebfcdg
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 )
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
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
310 return str(_ansi_colors[color] + offset) 1aebfcdg
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
335 bits = [] 1aebfcdg
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
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
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
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
383 return echo(message, file=file, nl=nl, err=err, color=color) 1aebfcdg
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
395 return open_url(url, wait=wait, locate=locate) 1aebfcdg
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
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.
411 Note that this will always read from the terminal, even if something
412 is piped into the standard input.
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
420 if _getchar is None: 1aebfcdg
421 from ._termui_impl import getchar as f 1aebfcdg
423 _getchar = f 1aebfcdg
425 return _getchar(echo) 1aebfcdg
428def raw_terminal() -> AbstractContextManager[int]: 1aebfcdg
429 from ._termui_impl import raw_terminal as f 1aebfcdg
431 return f() 1aebfcdg