Coverage for typer / core.py: 100%
323 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-26 21:46 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-26 21:46 +0000
1import errno 1aefcdbg
2import inspect 1aefcdbg
3import os 1aefcdbg
4import sys 1aefcdbg
5from collections.abc import Callable, MutableMapping, Sequence 1aefcdbg
6from difflib import get_close_matches 1aefcdbg
7from enum import Enum 1aefcdbg
8from gettext import gettext as _ 1aefcdbg
9from typing import ( 1aefcdbg
10 Any,
11 TextIO,
12 Union,
13 cast,
14)
16import click 1aefcdbg
17import click.core 1aefcdbg
18import click.formatting 1aefcdbg
19import click.shell_completion 1aefcdbg
20import click.types 1aefcdbg
21import click.utils 1aefcdbg
23from ._typing import Literal 1aefcdbg
24from .utils import parse_boolean_env_var 1aefcdbg
26MarkupMode = Literal["markdown", "rich", None] 1aefcdbg
27MARKUP_MODE_KEY = "TYPER_RICH_MARKUP_MODE" 1aefcdbg
29HAS_RICH = parse_boolean_env_var(os.getenv("TYPER_USE_RICH"), default=True) 1aefcdbg
31if HAS_RICH: 1aefcdbg
32 DEFAULT_MARKUP_MODE: MarkupMode = "rich" 1aefcdbg
33else:
34 DEFAULT_MARKUP_MODE = None 1aefcdbg
37# Copy from click.parser._split_opt
38def _split_opt(opt: str) -> tuple[str, str]: 1aefcdbg
39 first = opt[:1] 1aefcdbg
40 if first.isalnum(): 1aefcdbg
41 return "", opt 1aefcdbg
42 if opt[1:2] == first: 1aefcdbg
43 return opt[:2], opt[2:] 1aefcdbg
44 return first, opt[1:] 1aefcdbg
47def _typer_param_setup_autocompletion_compat( 1aefcdbg
48 self: click.Parameter,
49 *,
50 autocompletion: Callable[
51 [click.Context, list[str], str], list[tuple[str, str] | str]
52 ]
53 | None = None,
54) -> None:
55 if self._custom_shell_complete is not None: 1aefcdbg
56 import warnings 1aefcdbg
58 warnings.warn( 1aefcdbg
59 "In Typer, only the parameter 'autocompletion' is supported. "
60 "The support for 'shell_complete' is deprecated and will be removed in upcoming versions. ",
61 DeprecationWarning,
62 stacklevel=2,
63 )
65 if autocompletion is not None: 1aefcdbg
67 def compat_autocompletion( 1aefcdbg
68 ctx: click.Context, param: click.core.Parameter, incomplete: str
69 ) -> list["click.shell_completion.CompletionItem"]:
70 from click.shell_completion import CompletionItem 1aefcdbg
72 out = [] 1aefcdbg
74 for c in autocompletion(ctx, [], incomplete): 1aefcdbg
75 if isinstance(c, tuple): 1aefcdbg
76 use_completion = CompletionItem(c[0], help=c[1]) 1aefcdbg
77 else:
78 assert isinstance(c, str) 1aefcdbg
79 use_completion = CompletionItem(c) 1aefcdbg
81 if use_completion.value.startswith(incomplete): 1aefcdbg
82 out.append(use_completion) 1aefcdbg
84 return out 1aefcdbg
86 self._custom_shell_complete = compat_autocompletion 1aefcdbg
89def _get_default_string( 1aefcdbg
90 obj: Union["TyperArgument", "TyperOption"],
91 *,
92 ctx: click.Context,
93 show_default_is_str: bool,
94 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
95) -> str:
96 # Extracted from click.core.Option.get_help_record() to be reused by
97 # rich_utils avoiding RegEx hacks
98 if show_default_is_str: 1aefcdbg
99 default_string = f"({obj.show_default})" 1aefcdbg
100 elif isinstance(default_value, (list, tuple)): 1aefcdbg
101 default_string = ", ".join( 1aefcdbg
102 _get_default_string(
103 obj, ctx=ctx, show_default_is_str=show_default_is_str, default_value=d
104 )
105 for d in default_value
106 )
107 elif isinstance(default_value, Enum): 1aefcdbg
108 default_string = str(default_value.value) 1aefcdbg
109 elif inspect.isfunction(default_value): 1aefcdbg
110 default_string = _("(dynamic)") 1aefcdbg
111 elif isinstance(obj, TyperOption) and obj.is_bool_flag and obj.secondary_opts: 1aefcdbg
112 # For boolean flags that have distinct True/False opts,
113 # use the opt without prefix instead of the value.
114 # Typer override, original commented
115 # default_string = click.parser.split_opt(
116 # (self.opts if self.default else self.secondary_opts)[0]
117 # )[1]
118 if obj.default: 1aefcdbg
119 if obj.opts: 1aefcdbg
120 default_string = _split_opt(obj.opts[0])[1] 1aefcdbg
121 else:
122 default_string = str(default_value) 1aefcdbg
123 else:
124 default_string = _split_opt(obj.secondary_opts[0])[1] 1aefcdbg
125 # Typer override end
126 elif ( 1ab
127 isinstance(obj, TyperOption)
128 and obj.is_bool_flag
129 and not obj.secondary_opts
130 and not default_value
131 ):
132 default_string = "" 1aefcdbg
133 else:
134 default_string = str(default_value) 1aefcdbg
135 return default_string 1aefcdbg
138def _extract_default_help_str( 1aefcdbg
139 obj: Union["TyperArgument", "TyperOption"], *, ctx: click.Context
140) -> Any | Callable[[], Any] | None:
141 # Extracted from click.core.Option.get_help_record() to be reused by
142 # rich_utils avoiding RegEx hacks
143 # Temporarily enable resilient parsing to avoid type casting
144 # failing for the default. Might be possible to extend this to
145 # help formatting in general.
146 resilient = ctx.resilient_parsing 1aefcdbg
147 ctx.resilient_parsing = True 1aefcdbg
149 try: 1aefcdbg
150 default_value = obj.get_default(ctx, call=False) 1aefcdbg
151 finally:
152 ctx.resilient_parsing = resilient 1aefcdbg
153 return default_value 1aefcdbg
156def _main( 1aefcdbg
157 self: click.Command,
158 *,
159 args: Sequence[str] | None = None,
160 prog_name: str | None = None,
161 complete_var: str | None = None,
162 standalone_mode: bool = True,
163 windows_expand_args: bool = True,
164 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
165 **extra: Any,
166) -> Any:
167 # Typer override, duplicated from click.main() to handle custom rich exceptions
168 # Verify that the environment is configured correctly, or reject
169 # further execution to avoid a broken script.
170 if args is None: 1aefcdbg
171 args = sys.argv[1:] 1aefcdbg
173 # Covered in Click tests
174 if os.name == "nt" and windows_expand_args: # pragma: no cover 1aefcdbg
175 args = click.utils._expand_args(args) 1cd
176 else:
177 args = list(args) 1aefcdbg
179 if prog_name is None: 1aefcdbg
180 prog_name = click.utils._detect_program_name() 1aefcdbg
182 # Process shell completion requests and exit early.
183 self._main_shell_completion(extra, prog_name, complete_var) 1aefcdbg
185 try: 1aefcdbg
186 try: 1aefcdbg
187 with self.make_context(prog_name, args, **extra) as ctx: 1aefcdbg
188 rv = self.invoke(ctx) 1aefcdbg
189 if not standalone_mode: 1aefcdbg
190 return rv 1aefcdbg
191 # it's not safe to `ctx.exit(rv)` here!
192 # note that `rv` may actually contain data like "1" which
193 # has obvious effects
194 # more subtle case: `rv=[None, None]` can come out of
195 # chained commands which all returned `None` -- so it's not
196 # even always obvious that `rv` indicates success/failure
197 # by its truthiness/falsiness
198 ctx.exit() 1aefcdbg
199 except EOFError as e: 1aefcdbg
200 click.echo(file=sys.stderr) 1aefcdbg
201 raise click.Abort() from e 1aefcdbg
202 except KeyboardInterrupt as e: 1aefcdbg
203 raise click.exceptions.Exit(130) from e 1aefcdbg
204 except click.ClickException as e: 1aefcdbg
205 if not standalone_mode: 1aefcdbg
206 raise 1aefcdbg
207 # Typer override
208 if HAS_RICH and rich_markup_mode is not None: 1aefcdbg
209 from . import rich_utils 1aefcdbg
211 rich_utils.rich_format_error(e) 1aefcdbg
212 else:
213 e.show() 1aefcdbg
214 # Typer override end
215 sys.exit(e.exit_code) 1aefcdbg
216 except OSError as e: 1aefcdbg
217 if e.errno == errno.EPIPE: 1aefcdbg
218 sys.stdout = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stdout)) 1aefcdbg
219 sys.stderr = cast(TextIO, click.utils.PacifyFlushWrapper(sys.stderr)) 1aefcdbg
220 sys.exit(1) 1aefcdbg
221 else:
222 raise 1aefcdbg
223 except click.exceptions.Exit as e: 1aefcdbg
224 if standalone_mode: 1aefcdbg
225 sys.exit(e.exit_code) 1aefcdbg
226 else:
227 # in non-standalone mode, return the exit code
228 # note that this is only reached if `self.invoke` above raises
229 # an Exit explicitly -- thus bypassing the check there which
230 # would return its result
231 # the results of non-standalone execution may therefore be
232 # somewhat ambiguous: if there are codepaths which lead to
233 # `ctx.exit(1)` and to `return 1`, the caller won't be able to
234 # tell the difference between the two
235 return e.exit_code 1aefcdbg
236 except click.Abort: 1aefcdbg
237 if not standalone_mode: 1aefcdbg
238 raise 1aefcdbg
239 # Typer override
240 if HAS_RICH and rich_markup_mode is not None: 1aefcdbg
241 from . import rich_utils 1aefcdbg
243 rich_utils.rich_abort_error() 1aefcdbg
244 else:
245 click.echo(_("Aborted!"), file=sys.stderr) 1aefcdbg
246 # Typer override end
247 sys.exit(1) 1aefcdbg
250class TyperArgument(click.core.Argument): 1aefcdbg
251 def __init__( 1aefcdbg
252 self,
253 *,
254 # Parameter
255 param_decls: list[str],
256 type: Any | None = None,
257 required: bool | None = None,
258 default: Any | None = None,
259 callback: Callable[..., Any] | None = None,
260 nargs: int | None = None,
261 metavar: str | None = None,
262 expose_value: bool = True,
263 is_eager: bool = False,
264 envvar: str | list[str] | None = None,
265 # Note that shell_complete is not fully supported and will be removed in future versions
266 # TODO: Remove shell_complete in a future version (after 0.16.0)
267 shell_complete: Callable[
268 [click.Context, click.Parameter, str],
269 list["click.shell_completion.CompletionItem"] | list[str],
270 ]
271 | None = None,
272 autocompletion: Callable[..., Any] | None = None,
273 # TyperArgument
274 show_default: bool | str = True,
275 show_choices: bool = True,
276 show_envvar: bool = True,
277 help: str | None = None,
278 hidden: bool = False,
279 # Rich settings
280 rich_help_panel: str | None = None,
281 ):
282 self.help = help 1aefcdbg
283 self.show_default = show_default 1aefcdbg
284 self.show_choices = show_choices 1aefcdbg
285 self.show_envvar = show_envvar 1aefcdbg
286 self.hidden = hidden 1aefcdbg
287 self.rich_help_panel = rich_help_panel 1aefcdbg
289 super().__init__( 1aefcdbg
290 param_decls=param_decls,
291 type=type,
292 required=required,
293 default=default,
294 callback=callback,
295 nargs=nargs,
296 metavar=metavar,
297 expose_value=expose_value,
298 is_eager=is_eager,
299 envvar=envvar,
300 shell_complete=shell_complete,
301 )
302 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg
304 def _get_default_string( 1aefcdbg
305 self,
306 *,
307 ctx: click.Context,
308 show_default_is_str: bool,
309 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
310 ) -> str:
311 return _get_default_string( 1aefcdbg
312 self,
313 ctx=ctx,
314 show_default_is_str=show_default_is_str,
315 default_value=default_value,
316 )
318 def _extract_default_help_str( 1aefcdbg
319 self, *, ctx: click.Context
320 ) -> Any | Callable[[], Any] | None:
321 return _extract_default_help_str(self, ctx=ctx) 1aefcdbg
323 def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None: 1aefcdbg
324 # Modified version of click.core.Option.get_help_record()
325 # to support Arguments
326 if self.hidden: 1aefcdbg
327 return None 1aefcdbg
328 name = self.make_metavar(ctx=ctx) 1aefcdbg
329 help = self.help or "" 1aefcdbg
330 extra = [] 1aefcdbg
331 if self.show_envvar: 1aefcdbg
332 envvar = self.envvar 1aefcdbg
333 # allow_from_autoenv is currently not supported in Typer for CLI Arguments
334 if envvar is not None: 1aefcdbg
335 var_str = ( 1aefcdbg
336 ", ".join(str(d) for d in envvar)
337 if isinstance(envvar, (list, tuple))
338 else envvar
339 )
340 extra.append(f"env var: {var_str}") 1aefcdbg
342 # Typer override:
343 # Extracted to _extract_default_help_str() to allow re-using it in rich_utils
344 default_value = self._extract_default_help_str(ctx=ctx) 1aefcdbg
345 # Typer override end
347 show_default_is_str = isinstance(self.show_default, str) 1aefcdbg
349 if show_default_is_str or ( 1aefcdbg
350 default_value is not None and (self.show_default or ctx.show_default)
351 ):
352 # Typer override:
353 # Extracted to _get_default_string() to allow re-using it in rich_utils
354 default_string = self._get_default_string( 1aefcdbg
355 ctx=ctx,
356 show_default_is_str=show_default_is_str,
357 default_value=default_value,
358 )
359 # Typer override end
360 if default_string: 1aefcdbg
361 extra.append(_("default: {default}").format(default=default_string)) 1aefcdbg
362 if self.required: 1aefcdbg
363 extra.append(_("required")) 1aefcdbg
364 if extra: 1aefcdbg
365 extra_str = "; ".join(extra) 1aefcdbg
366 extra_str = f"[{extra_str}]" 1aefcdbg
367 rich_markup_mode = None 1aefcdbg
368 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1aefcdbg
369 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1aefcdbg
370 if HAS_RICH and rich_markup_mode == "rich": 1aefcdbg
371 # This is needed for when we want to export to HTML
372 from . import rich_utils 1aefcdbg
374 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg
376 help = f"{help} {extra_str}" if help else f"{extra_str}" 1aefcdbg
377 return name, help 1aefcdbg
379 def make_metavar(self, ctx: click.Context) -> str: 1aefcdbg
380 # Modified version of click.core.Argument.make_metavar()
381 # to include Argument name
382 if self.metavar is not None: 1aefcdbg
383 var = self.metavar 1aefcdbg
384 if not self.required and not var.startswith("["): 1aefcdbg
385 var = f"[{var}]" 1aefcdbg
386 return var 1aefcdbg
387 var = (self.name or "").upper() 1aefcdbg
388 if not self.required: 1aefcdbg
389 var = f"[{var}]" 1aefcdbg
390 type_var = self.type.get_metavar(self, ctx=ctx) 1aefcdbg
391 if type_var: 1aefcdbg
392 var += f":{type_var}" 1aefcdbg
393 if self.nargs != 1: 1aefcdbg
394 var += "..." 1aefcdbg
395 return var 1aefcdbg
397 def value_is_missing(self, value: Any) -> bool: 1aefcdbg
398 return _value_is_missing(self, value) 1aefcdbg
401class TyperOption(click.core.Option): 1aefcdbg
402 def __init__( 1aefcdbg
403 self,
404 *,
405 # Parameter
406 param_decls: list[str],
407 type: click.types.ParamType | Any | None = None,
408 required: bool | None = None,
409 default: Any | None = None,
410 callback: Callable[..., Any] | None = None,
411 nargs: int | None = None,
412 metavar: str | None = None,
413 expose_value: bool = True,
414 is_eager: bool = False,
415 envvar: str | list[str] | None = None,
416 # Note that shell_complete is not fully supported and will be removed in future versions
417 # TODO: Remove shell_complete in a future version (after 0.16.0)
418 shell_complete: Callable[
419 [click.Context, click.Parameter, str],
420 list["click.shell_completion.CompletionItem"] | list[str],
421 ]
422 | None = None,
423 autocompletion: Callable[..., Any] | None = None,
424 # Option
425 show_default: bool | str = False,
426 prompt: bool | str = False,
427 confirmation_prompt: bool | str = False,
428 prompt_required: bool = True,
429 hide_input: bool = False,
430 is_flag: bool | None = None,
431 multiple: bool = False,
432 count: bool = False,
433 allow_from_autoenv: bool = True,
434 help: str | None = None,
435 hidden: bool = False,
436 show_choices: bool = True,
437 show_envvar: bool = False,
438 # Rich settings
439 rich_help_panel: str | None = None,
440 ):
441 super().__init__( 1aefcdbg
442 param_decls=param_decls,
443 type=type,
444 required=required,
445 default=default,
446 callback=callback,
447 nargs=nargs,
448 metavar=metavar,
449 expose_value=expose_value,
450 is_eager=is_eager,
451 envvar=envvar,
452 show_default=show_default,
453 prompt=prompt,
454 confirmation_prompt=confirmation_prompt,
455 hide_input=hide_input,
456 is_flag=is_flag,
457 multiple=multiple,
458 count=count,
459 allow_from_autoenv=allow_from_autoenv,
460 help=help,
461 hidden=hidden,
462 show_choices=show_choices,
463 show_envvar=show_envvar,
464 prompt_required=prompt_required,
465 shell_complete=shell_complete,
466 )
467 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg
468 self.rich_help_panel = rich_help_panel 1aefcdbg
470 def _get_default_string( 1aefcdbg
471 self,
472 *,
473 ctx: click.Context,
474 show_default_is_str: bool,
475 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
476 ) -> str:
477 return _get_default_string( 1aefcdbg
478 self,
479 ctx=ctx,
480 show_default_is_str=show_default_is_str,
481 default_value=default_value,
482 )
484 def _extract_default_help_str( 1aefcdbg
485 self, *, ctx: click.Context
486 ) -> Any | Callable[[], Any] | None:
487 return _extract_default_help_str(self, ctx=ctx) 1aefcdbg
489 def make_metavar(self, ctx: click.Context) -> str: 1aefcdbg
490 return super().make_metavar(ctx=ctx) 1aefcdbg
492 def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None: 1aefcdbg
493 # Duplicate all of Click's logic only to modify a single line, to allow boolean
494 # flags with only names for False values as it's currently supported by Typer
495 # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false
496 if self.hidden: 1aefcdbg
497 return None 1aefcdbg
499 any_prefix_is_slash = False 1aefcdbg
501 def _write_opts(opts: Sequence[str]) -> str: 1aefcdbg
502 nonlocal any_prefix_is_slash
504 rv, any_slashes = click.formatting.join_options(opts) 1aefcdbg
506 if any_slashes: 1aefcdbg
507 any_prefix_is_slash = True 1aefcdbg
509 if not self.is_flag and not self.count: 1aefcdbg
510 rv += f" {self.make_metavar(ctx=ctx)}" 1aefcdbg
512 return rv 1aefcdbg
514 rv = [_write_opts(self.opts)] 1aefcdbg
516 if self.secondary_opts: 1aefcdbg
517 rv.append(_write_opts(self.secondary_opts)) 1aefcdbg
519 help = self.help or "" 1aefcdbg
520 extra = [] 1aefcdbg
522 if self.show_envvar: 1aefcdbg
523 envvar = self.envvar 1aefcdbg
525 if envvar is None: 1aefcdbg
526 if ( 1ab
527 self.allow_from_autoenv
528 and ctx.auto_envvar_prefix is not None
529 and self.name is not None
530 ):
531 envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" 1aefcdbg
533 if envvar is not None: 1aefcdbg
534 var_str = ( 1aefcdbg
535 envvar
536 if isinstance(envvar, str)
537 else ", ".join(str(d) for d in envvar)
538 )
539 extra.append(_("env var: {var}").format(var=var_str)) 1aefcdbg
541 # Typer override:
542 # Extracted to _extract_default() to allow re-using it in rich_utils
543 default_value = self._extract_default_help_str(ctx=ctx) 1aefcdbg
544 # Typer override end
546 show_default_is_str = isinstance(self.show_default, str) 1aefcdbg
548 if show_default_is_str or ( 1aefcdbg
549 default_value is not None and (self.show_default or ctx.show_default)
550 ):
551 # Typer override:
552 # Extracted to _get_default_string() to allow re-using it in rich_utils
553 default_string = self._get_default_string( 1aefcdbg
554 ctx=ctx,
555 show_default_is_str=show_default_is_str,
556 default_value=default_value,
557 )
558 # Typer override end
559 if default_string: 1aefcdbg
560 extra.append(_("default: {default}").format(default=default_string)) 1aefcdbg
562 if isinstance(self.type, click.types._NumberRangeBase): 1aefcdbg
563 range_str = self.type._describe_range() 1aefcdbg
565 if range_str: 1aefcdbg
566 extra.append(range_str) 1aefcdbg
568 if self.required: 1aefcdbg
569 extra.append(_("required")) 1aefcdbg
571 if extra: 1aefcdbg
572 extra_str = "; ".join(extra) 1aefcdbg
573 extra_str = f"[{extra_str}]" 1aefcdbg
574 rich_markup_mode = None 1aefcdbg
575 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1aefcdbg
576 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1aefcdbg
577 if HAS_RICH and rich_markup_mode == "rich": 1aefcdbg
578 # This is needed for when we want to export to HTML
579 from . import rich_utils 1aefcdbg
581 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg
583 help = f"{help} {extra_str}" if help else f"{extra_str}" 1aefcdbg
585 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1aefcdbg
587 def value_is_missing(self, value: Any) -> bool: 1aefcdbg
588 return _value_is_missing(self, value) 1aefcdbg
591def _value_is_missing(param: click.Parameter, value: Any) -> bool: 1aefcdbg
592 if value is None: 1aefcdbg
593 return True 1aefcdbg
595 # Click 8.3 and beyond
596 # if value is UNSET:
597 # return True
599 if (param.nargs != 1 or param.multiple) and value == (): 1aefcdbg
600 return True # pragma: no cover
602 return False 1aefcdbg
605def _typer_format_options( 1aefcdbg
606 self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter
607) -> None:
608 args = [] 1aefcdbg
609 opts = [] 1aefcdbg
610 for param in self.get_params(ctx): 1aefcdbg
611 rv = param.get_help_record(ctx) 1aefcdbg
612 if rv is not None: 1aefcdbg
613 if param.param_type_name == "argument": 1aefcdbg
614 args.append(rv) 1aefcdbg
615 elif param.param_type_name == "option": 1aefcdbg
616 opts.append(rv) 1aefcdbg
618 if args: 1aefcdbg
619 with formatter.section(_("Arguments")): 1aefcdbg
620 formatter.write_dl(args) 1aefcdbg
621 if opts: 1aefcdbg
622 with formatter.section(_("Options")): 1aefcdbg
623 formatter.write_dl(opts) 1aefcdbg
626def _typer_main_shell_completion( 1aefcdbg
627 self: click.core.Command,
628 *,
629 ctx_args: MutableMapping[str, Any],
630 prog_name: str,
631 complete_var: str | None = None,
632) -> None:
633 if complete_var is None: 1aefcdbg
634 complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() 1aefcdbg
636 instruction = os.environ.get(complete_var) 1aefcdbg
638 if not instruction: 1aefcdbg
639 return 1aefcdbg
641 from .completion import shell_complete 1aefcdbg
643 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1aefcdbg
644 sys.exit(rv) 1aefcdbg
647class TyperCommand(click.core.Command): 1aefcdbg
648 def __init__( 1aefcdbg
649 self,
650 name: str | None,
651 *,
652 context_settings: dict[str, Any] | None = None,
653 callback: Callable[..., Any] | None = None,
654 params: list[click.Parameter] | None = None,
655 help: str | None = None,
656 epilog: str | None = None,
657 short_help: str | None = None,
658 options_metavar: str | None = "[OPTIONS]",
659 add_help_option: bool = True,
660 no_args_is_help: bool = False,
661 hidden: bool = False,
662 deprecated: bool = False,
663 # Rich settings
664 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
665 rich_help_panel: str | None = None,
666 ) -> None:
667 super().__init__( 1aefcdbg
668 name=name,
669 context_settings=context_settings,
670 callback=callback,
671 params=params,
672 help=help,
673 epilog=epilog,
674 short_help=short_help,
675 options_metavar=options_metavar,
676 add_help_option=add_help_option,
677 no_args_is_help=no_args_is_help,
678 hidden=hidden,
679 deprecated=deprecated,
680 )
681 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg
682 self.rich_help_panel = rich_help_panel 1aefcdbg
684 def format_options( 1aefcdbg
685 self, ctx: click.Context, formatter: click.HelpFormatter
686 ) -> None:
687 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg
689 def _main_shell_completion( 1aefcdbg
690 self,
691 ctx_args: MutableMapping[str, Any],
692 prog_name: str,
693 complete_var: str | None = None,
694 ) -> None:
695 _typer_main_shell_completion( 1aefcdbg
696 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
697 )
699 def main( 1aefcdbg
700 self,
701 args: Sequence[str] | None = None,
702 prog_name: str | None = None,
703 complete_var: str | None = None,
704 standalone_mode: bool = True,
705 windows_expand_args: bool = True,
706 **extra: Any,
707 ) -> Any:
708 return _main( 1aefcdbg
709 self,
710 args=args,
711 prog_name=prog_name,
712 complete_var=complete_var,
713 standalone_mode=standalone_mode,
714 windows_expand_args=windows_expand_args,
715 rich_markup_mode=self.rich_markup_mode,
716 **extra,
717 )
719 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1aefcdbg
720 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg
721 if not hasattr(ctx, "obj") or ctx.obj is None: 1aefcdbg
722 ctx.ensure_object(dict) 1aefcdbg
723 if isinstance(ctx.obj, dict): 1aefcdbg
724 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1aefcdbg
725 return super().format_help(ctx, formatter) 1aefcdbg
726 from . import rich_utils 1aefcdbg
728 return rich_utils.rich_format_help( 1aefcdbg
729 obj=self,
730 ctx=ctx,
731 markup_mode=self.rich_markup_mode,
732 )
735class TyperGroup(click.core.Group): 1aefcdbg
736 def __init__( 1aefcdbg
737 self,
738 *,
739 name: str | None = None,
740 commands: dict[str, click.Command] | Sequence[click.Command] | None = None,
741 # Rich settings
742 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
743 rich_help_panel: str | None = None,
744 suggest_commands: bool = True,
745 **attrs: Any,
746 ) -> None:
747 super().__init__(name=name, commands=commands, **attrs) 1aefcdbg
748 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg
749 self.rich_help_panel = rich_help_panel 1aefcdbg
750 self.suggest_commands = suggest_commands 1aefcdbg
752 def format_options( 1aefcdbg
753 self, ctx: click.Context, formatter: click.HelpFormatter
754 ) -> None:
755 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg
756 self.format_commands(ctx, formatter) 1aefcdbg
758 def _main_shell_completion( 1aefcdbg
759 self,
760 ctx_args: MutableMapping[str, Any],
761 prog_name: str,
762 complete_var: str | None = None,
763 ) -> None:
764 _typer_main_shell_completion( 1aefcdbg
765 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
766 )
768 def resolve_command( 1aefcdbg
769 self, ctx: click.Context, args: list[str]
770 ) -> tuple[str | None, click.Command | None, list[str]]:
771 try: 1aefcdbg
772 return super().resolve_command(ctx, args) 1aefcdbg
773 except click.UsageError as e: 1aefcdbg
774 if self.suggest_commands: 1aefcdbg
775 available_commands = list(self.commands.keys()) 1aefcdbg
776 if available_commands and args: 1aefcdbg
777 typo = args[0] 1aefcdbg
778 matches = get_close_matches(typo, available_commands) 1aefcdbg
779 if matches: 1aefcdbg
780 suggestions = ", ".join(f"{m!r}" for m in matches) 1aefcdbg
781 message = e.message.rstrip(".") 1aefcdbg
782 e.message = f"{message}. Did you mean {suggestions}?" 1aefcdbg
783 raise 1aefcdbg
785 def main( 1aefcdbg
786 self,
787 args: Sequence[str] | None = None,
788 prog_name: str | None = None,
789 complete_var: str | None = None,
790 standalone_mode: bool = True,
791 windows_expand_args: bool = True,
792 **extra: Any,
793 ) -> Any:
794 return _main( 1aefcdbg
795 self,
796 args=args,
797 prog_name=prog_name,
798 complete_var=complete_var,
799 standalone_mode=standalone_mode,
800 windows_expand_args=windows_expand_args,
801 rich_markup_mode=self.rich_markup_mode,
802 **extra,
803 )
805 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1aefcdbg
806 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg
807 return super().format_help(ctx, formatter) 1aefcdbg
808 from . import rich_utils 1aefcdbg
810 return rich_utils.rich_format_help( 1aefcdbg
811 obj=self,
812 ctx=ctx,
813 markup_mode=self.rich_markup_mode,
814 )
816 def list_commands(self, ctx: click.Context) -> list[str]: 1aefcdbg
817 """Returns a list of subcommand names.
818 Note that in Click's Group class, these are sorted.
819 In Typer, we wish to maintain the original order of creation (cf Issue #933)"""
820 return [n for n, c in self.commands.items()] 1aefcdbg