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