Coverage for typer / core.py: 100%
323 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-09 16:39 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-03-09 16:39 +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 | None = None) -> 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) # type: ignore[arg-type] 1aefcdbg
391 # type_var = self.type.get_metavar(self, ctx=ctx)
392 if type_var: 1aefcdbg
393 var += f":{type_var}" 1aefcdbg
394 if self.nargs != 1: 1aefcdbg
395 var += "..." 1aefcdbg
396 return var 1aefcdbg
398 def value_is_missing(self, value: Any) -> bool: 1aefcdbg
399 return _value_is_missing(self, value) 1aefcdbg
402class TyperOption(click.core.Option): 1aefcdbg
403 def __init__( 1aefcdbg
404 self,
405 *,
406 # Parameter
407 param_decls: list[str],
408 type: click.types.ParamType | Any | None = None,
409 required: bool | None = None,
410 default: Any | None = None,
411 callback: Callable[..., Any] | None = None,
412 nargs: int | None = None,
413 metavar: str | None = None,
414 expose_value: bool = True,
415 is_eager: bool = False,
416 envvar: str | list[str] | None = None,
417 # Note that shell_complete is not fully supported and will be removed in future versions
418 # TODO: Remove shell_complete in a future version (after 0.16.0)
419 shell_complete: Callable[
420 [click.Context, click.Parameter, str],
421 list["click.shell_completion.CompletionItem"] | list[str],
422 ]
423 | None = None,
424 autocompletion: Callable[..., Any] | None = None,
425 # Option
426 show_default: bool | str = False,
427 prompt: bool | str = False,
428 confirmation_prompt: bool | str = False,
429 prompt_required: bool = True,
430 hide_input: bool = False,
431 is_flag: bool | None = None,
432 multiple: bool = False,
433 count: bool = False,
434 allow_from_autoenv: bool = True,
435 help: str | None = None,
436 hidden: bool = False,
437 show_choices: bool = True,
438 show_envvar: bool = False,
439 # Rich settings
440 rich_help_panel: str | None = None,
441 ):
442 super().__init__( 1aefcdbg
443 param_decls=param_decls,
444 type=type,
445 required=required,
446 default=default,
447 callback=callback,
448 nargs=nargs,
449 metavar=metavar,
450 expose_value=expose_value,
451 is_eager=is_eager,
452 envvar=envvar,
453 show_default=show_default,
454 prompt=prompt,
455 confirmation_prompt=confirmation_prompt,
456 hide_input=hide_input,
457 is_flag=is_flag,
458 multiple=multiple,
459 count=count,
460 allow_from_autoenv=allow_from_autoenv,
461 help=help,
462 hidden=hidden,
463 show_choices=show_choices,
464 show_envvar=show_envvar,
465 prompt_required=prompt_required,
466 shell_complete=shell_complete,
467 )
468 _typer_param_setup_autocompletion_compat(self, autocompletion=autocompletion) 1aefcdbg
469 self.rich_help_panel = rich_help_panel 1aefcdbg
471 def _get_default_string( 1aefcdbg
472 self,
473 *,
474 ctx: click.Context,
475 show_default_is_str: bool,
476 default_value: list[Any] | tuple[Any, ...] | str | Callable[..., Any] | Any,
477 ) -> str:
478 return _get_default_string( 1aefcdbg
479 self,
480 ctx=ctx,
481 show_default_is_str=show_default_is_str,
482 default_value=default_value,
483 )
485 def _extract_default_help_str( 1aefcdbg
486 self, *, ctx: click.Context
487 ) -> Any | Callable[[], Any] | None:
488 return _extract_default_help_str(self, ctx=ctx) 1aefcdbg
490 def make_metavar(self, ctx: click.Context | None = None) -> str: 1aefcdbg
491 return super().make_metavar(ctx=ctx) # type: ignore[arg-type] 1aefcdbg
493 def get_help_record(self, ctx: click.Context) -> tuple[str, str] | None: 1aefcdbg
494 # Duplicate all of Click's logic only to modify a single line, to allow boolean
495 # flags with only names for False values as it's currently supported by Typer
496 # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false
497 if self.hidden: 1aefcdbg
498 return None 1aefcdbg
500 any_prefix_is_slash = False 1aefcdbg
502 def _write_opts(opts: Sequence[str]) -> str: 1aefcdbg
503 nonlocal any_prefix_is_slash
505 rv, any_slashes = click.formatting.join_options(opts) 1aefcdbg
507 if any_slashes: 1aefcdbg
508 any_prefix_is_slash = True 1aefcdbg
510 if not self.is_flag and not self.count: 1aefcdbg
511 rv += f" {self.make_metavar(ctx=ctx)}" 1aefcdbg
513 return rv 1aefcdbg
515 rv = [_write_opts(self.opts)] 1aefcdbg
517 if self.secondary_opts: 1aefcdbg
518 rv.append(_write_opts(self.secondary_opts)) 1aefcdbg
520 help = self.help or "" 1aefcdbg
521 extra = [] 1aefcdbg
523 if self.show_envvar: 1aefcdbg
524 envvar = self.envvar 1aefcdbg
526 if envvar is None: 1aefcdbg
527 if ( 1ab
528 self.allow_from_autoenv
529 and ctx.auto_envvar_prefix is not None
530 and self.name is not None
531 ):
532 envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" 1aefcdbg
534 if envvar is not None: 1aefcdbg
535 var_str = ( 1aefcdbg
536 envvar
537 if isinstance(envvar, str)
538 else ", ".join(str(d) for d in envvar)
539 )
540 extra.append(_("env var: {var}").format(var=var_str)) 1aefcdbg
542 # Typer override:
543 # Extracted to _extract_default() to allow re-using it in rich_utils
544 default_value = self._extract_default_help_str(ctx=ctx) 1aefcdbg
545 # Typer override end
547 show_default_is_str = isinstance(self.show_default, str) 1aefcdbg
549 if show_default_is_str or ( 1aefcdbg
550 default_value is not None and (self.show_default or ctx.show_default)
551 ):
552 # Typer override:
553 # Extracted to _get_default_string() to allow re-using it in rich_utils
554 default_string = self._get_default_string( 1aefcdbg
555 ctx=ctx,
556 show_default_is_str=show_default_is_str,
557 default_value=default_value,
558 )
559 # Typer override end
560 if default_string: 1aefcdbg
561 extra.append(_("default: {default}").format(default=default_string)) 1aefcdbg
563 if isinstance(self.type, click.types._NumberRangeBase): 1aefcdbg
564 range_str = self.type._describe_range() 1aefcdbg
566 if range_str: 1aefcdbg
567 extra.append(range_str) 1aefcdbg
569 if self.required: 1aefcdbg
570 extra.append(_("required")) 1aefcdbg
572 if extra: 1aefcdbg
573 extra_str = "; ".join(extra) 1aefcdbg
574 extra_str = f"[{extra_str}]" 1aefcdbg
575 rich_markup_mode = None 1aefcdbg
576 if hasattr(ctx, "obj") and isinstance(ctx.obj, dict): 1aefcdbg
577 rich_markup_mode = ctx.obj.get(MARKUP_MODE_KEY, None) 1aefcdbg
578 if HAS_RICH and rich_markup_mode == "rich": 1aefcdbg
579 # This is needed for when we want to export to HTML
580 from . import rich_utils 1aefcdbg
582 extra_str = rich_utils.escape_before_html_export(extra_str) 1aefcdbg
584 help = f"{help} {extra_str}" if help else f"{extra_str}" 1aefcdbg
586 return ("; " if any_prefix_is_slash else " / ").join(rv), help 1aefcdbg
588 def value_is_missing(self, value: Any) -> bool: 1aefcdbg
589 return _value_is_missing(self, value) 1aefcdbg
592def _value_is_missing(param: click.Parameter, value: Any) -> bool: 1aefcdbg
593 if value is None: 1aefcdbg
594 return True 1aefcdbg
596 # Click 8.3 and beyond
597 # if value is UNSET:
598 # return True
600 if (param.nargs != 1 or param.multiple) and value == (): 1aefcdbg
601 return True # pragma: no cover
603 return False 1aefcdbg
606def _typer_format_options( 1aefcdbg
607 self: click.core.Command, *, ctx: click.Context, formatter: click.HelpFormatter
608) -> None:
609 args = [] 1aefcdbg
610 opts = [] 1aefcdbg
611 for param in self.get_params(ctx): 1aefcdbg
612 rv = param.get_help_record(ctx) 1aefcdbg
613 if rv is not None: 1aefcdbg
614 if param.param_type_name == "argument": 1aefcdbg
615 args.append(rv) 1aefcdbg
616 elif param.param_type_name == "option": 1aefcdbg
617 opts.append(rv) 1aefcdbg
619 if args: 1aefcdbg
620 with formatter.section(_("Arguments")): 1aefcdbg
621 formatter.write_dl(args) 1aefcdbg
622 if opts: 1aefcdbg
623 with formatter.section(_("Options")): 1aefcdbg
624 formatter.write_dl(opts) 1aefcdbg
627def _typer_main_shell_completion( 1aefcdbg
628 self: click.core.Command,
629 *,
630 ctx_args: MutableMapping[str, Any],
631 prog_name: str,
632 complete_var: str | None = None,
633) -> None:
634 if complete_var is None: 1aefcdbg
635 complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() 1aefcdbg
637 instruction = os.environ.get(complete_var) 1aefcdbg
639 if not instruction: 1aefcdbg
640 return 1aefcdbg
642 from .completion import shell_complete 1aefcdbg
644 rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) 1aefcdbg
645 sys.exit(rv) 1aefcdbg
648class TyperCommand(click.core.Command): 1aefcdbg
649 def __init__( 1aefcdbg
650 self,
651 name: str | None,
652 *,
653 context_settings: dict[str, Any] | None = None,
654 callback: Callable[..., Any] | None = None,
655 params: list[click.Parameter] | None = None,
656 help: str | None = None,
657 epilog: str | None = None,
658 short_help: str | None = None,
659 options_metavar: str | None = "[OPTIONS]",
660 add_help_option: bool = True,
661 no_args_is_help: bool = False,
662 hidden: bool = False,
663 deprecated: bool = False,
664 # Rich settings
665 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
666 rich_help_panel: str | None = None,
667 ) -> None:
668 super().__init__( 1aefcdbg
669 name=name,
670 context_settings=context_settings,
671 callback=callback,
672 params=params,
673 help=help,
674 epilog=epilog,
675 short_help=short_help,
676 options_metavar=options_metavar,
677 add_help_option=add_help_option,
678 no_args_is_help=no_args_is_help,
679 hidden=hidden,
680 deprecated=deprecated,
681 )
682 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg
683 self.rich_help_panel = rich_help_panel 1aefcdbg
685 def format_options( 1aefcdbg
686 self, ctx: click.Context, formatter: click.HelpFormatter
687 ) -> None:
688 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg
690 def _main_shell_completion( 1aefcdbg
691 self,
692 ctx_args: MutableMapping[str, Any],
693 prog_name: str,
694 complete_var: str | None = None,
695 ) -> None:
696 _typer_main_shell_completion( 1aefcdbg
697 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
698 )
700 def main( 1aefcdbg
701 self,
702 args: Sequence[str] | None = None,
703 prog_name: str | None = None,
704 complete_var: str | None = None,
705 standalone_mode: bool = True,
706 windows_expand_args: bool = True,
707 **extra: Any,
708 ) -> Any:
709 return _main( 1aefcdbg
710 self,
711 args=args,
712 prog_name=prog_name,
713 complete_var=complete_var,
714 standalone_mode=standalone_mode,
715 windows_expand_args=windows_expand_args,
716 rich_markup_mode=self.rich_markup_mode,
717 **extra,
718 )
720 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1aefcdbg
721 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg
722 if not hasattr(ctx, "obj") or ctx.obj is None: 1aefcdbg
723 ctx.ensure_object(dict) 1aefcdbg
724 if isinstance(ctx.obj, dict): 1aefcdbg
725 ctx.obj[MARKUP_MODE_KEY] = self.rich_markup_mode 1aefcdbg
726 return super().format_help(ctx, formatter) 1aefcdbg
727 from . import rich_utils 1aefcdbg
729 return rich_utils.rich_format_help( 1aefcdbg
730 obj=self,
731 ctx=ctx,
732 markup_mode=self.rich_markup_mode,
733 )
736class TyperGroup(click.core.Group): 1aefcdbg
737 def __init__( 1aefcdbg
738 self,
739 *,
740 name: str | None = None,
741 commands: dict[str, click.Command] | Sequence[click.Command] | None = None,
742 # Rich settings
743 rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE,
744 rich_help_panel: str | None = None,
745 suggest_commands: bool = True,
746 **attrs: Any,
747 ) -> None:
748 super().__init__(name=name, commands=commands, **attrs) 1aefcdbg
749 self.rich_markup_mode: MarkupMode = rich_markup_mode 1aefcdbg
750 self.rich_help_panel = rich_help_panel 1aefcdbg
751 self.suggest_commands = suggest_commands 1aefcdbg
753 def format_options( 1aefcdbg
754 self, ctx: click.Context, formatter: click.HelpFormatter
755 ) -> None:
756 _typer_format_options(self, ctx=ctx, formatter=formatter) 1aefcdbg
757 self.format_commands(ctx, formatter) 1aefcdbg
759 def _main_shell_completion( 1aefcdbg
760 self,
761 ctx_args: MutableMapping[str, Any],
762 prog_name: str,
763 complete_var: str | None = None,
764 ) -> None:
765 _typer_main_shell_completion( 1aefcdbg
766 self, ctx_args=ctx_args, prog_name=prog_name, complete_var=complete_var
767 )
769 def resolve_command( 1aefcdbg
770 self, ctx: click.Context, args: list[str]
771 ) -> tuple[str | None, click.Command | None, list[str]]:
772 try: 1aefcdbg
773 return super().resolve_command(ctx, args) 1aefcdbg
774 except click.UsageError as e: 1aefcdbg
775 if self.suggest_commands: 1aefcdbg
776 available_commands = list(self.commands.keys()) 1aefcdbg
777 if available_commands and args: 1aefcdbg
778 typo = args[0] 1aefcdbg
779 matches = get_close_matches(typo, available_commands) 1aefcdbg
780 if matches: 1aefcdbg
781 suggestions = ", ".join(f"{m!r}" for m in matches) 1aefcdbg
782 message = e.message.rstrip(".") 1aefcdbg
783 e.message = f"{message}. Did you mean {suggestions}?" 1aefcdbg
784 raise 1aefcdbg
786 def main( 1aefcdbg
787 self,
788 args: Sequence[str] | None = None,
789 prog_name: str | None = None,
790 complete_var: str | None = None,
791 standalone_mode: bool = True,
792 windows_expand_args: bool = True,
793 **extra: Any,
794 ) -> Any:
795 return _main( 1aefcdbg
796 self,
797 args=args,
798 prog_name=prog_name,
799 complete_var=complete_var,
800 standalone_mode=standalone_mode,
801 windows_expand_args=windows_expand_args,
802 rich_markup_mode=self.rich_markup_mode,
803 **extra,
804 )
806 def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: 1aefcdbg
807 if not HAS_RICH or self.rich_markup_mode is None: 1aefcdbg
808 return super().format_help(ctx, formatter) 1aefcdbg
809 from . import rich_utils 1aefcdbg
811 return rich_utils.rich_format_help( 1aefcdbg
812 obj=self,
813 ctx=ctx,
814 markup_mode=self.rich_markup_mode,
815 )
817 def list_commands(self, ctx: click.Context) -> list[str]: 1aefcdbg
818 """Returns a list of subcommand names.
819 Note that in Click's Group class, these are sorted.
820 In Typer, we wish to maintain the original order of creation (cf Issue #933)"""
821 return [n for n, c in self.commands.items()] 1aefcdbg